树的直径,树的重心,树的分冶
来源:互联网 发布:淘宝大卖家 编辑:程序博客网 时间:2024/04/28 07:18
主要是利用了反证法:
假设 s-t这条路径为树的直径,或者称为树上的最长路
现有结论,从任意一点u出发搜到的最远的点一定是s、t中的一点,然后在从这个最远点开始搜,就可以搜到另一个最长路的端点,即用两遍广搜就可以找出树的最长路
证明:
1 设u为s-t路径上的一点,结论显然成立,否则设搜到的最远点为T则
dis(u,T) >dis(u,s) 且 dis(u,T)>dis(u,t) 则最长路不是s-t了,与假设矛盾
2 设u不为s-t路径上的点
首先明确,假如u走到了s-t路径上的一点,那么接下来的路径肯定都在s-t上了,而且终点为s或t,在1中已经证明过了
所以现在又有两种情况了:
1:u走到了s-t路径上的某点,假设为X,最后肯定走到某个端点,假设是t ,则路径总长度为dis(u,X)+dis(X,t)
2:u走到最远点的路径u-T与s-t无交点,则dis(u-T) >dis(u,X)+dis(X,t);显然,如果这个式子成立,
则dis(u,T)+dis(s,X)+dis(u,X)>dis(s,X)+dis(X,t)=dis(s,t)最长路不是s-t矛盾
附上一张第二种情况的图
树的“重心”的一些性质及动态维护
2011-08-24 20:31:13| 分类: 程序|字号 订阅
POJ 1655 - DP 树的重心,经典 #P
- #include <cstdio>
- #include <iostream>
- #include <fstream>
- #include <cstring>
- #include <string>
- #include <vector>
- #define OP(s) cout<<#s<<"="<<s<<" ";
- #define PP(s) cout<<#s<<"="<<s<<endl;
- using namespace std;
- int n;
- vector <int> adj[20010];
- int son[20010];
- bool vd[20010];
- int ans,asize = 1<<29;
- void DFS(int s)
- {
- vd[s] = 1;
- son[s] = 0;
- int blance = 0;
- int size = adj[s].size();
- for (int j = 0;j < size;j++)
- {
- int u = adj[s][j];
- if (vd[u]) continue;
- DFS(u);
- son[s] += son[u]+1;
- blance = max(blance,son[u]+1);
- }
- blance = max(blance,n - son[s] - 1);
- if (blance < asize || blance == asize && s < ans)
- ans = s,asize = blance;
- }
- int main()
- {
- // freopen("test.txt","r",stdin);
- int T;
- cin>>T;
- while(T--)
- {
- cin>>n;
- for (int i = 1;i <= n;i++) adj[i].clear();
- for (int i = 1;i <= n-1;i++)
- {
- int u,v;
- scanf("%d%d",&u,&v);
- adj[u].push_back(v);
- adj[v].push_back(u);
- }
- memset(vd,0,sizeof(vd));
- asize = 1<<29;
- DFS(1);
- cout<<ans<<" "<<asize<<endl;
- }
- return 0;
- }
poj 1741 (树的分治)
题意:给定一棵N(1<= N <=10000)个结点的带权树,定义dist(u,v)为u,v两点间的最短路径长度,路径的长度定义为路径上所有边的权和。再给定一个 K ,如果对于不同的两个结点a,b,如果满足dist(a,b) <=K,则称(a,b)为合法点对。求合法点对个数。
思路:看了论文《分治算法在树的路径问题中的应用》,里面讲解的很清楚,一条路径要么过根节点,要么在一颗子树中,所以用分治算法。找到树的重心作为根节点,这样每次树的节点数至少减少一半。处理经过当前根节点路径<=k的点对数,然后把根节点去掉后就把原来的树分成几颗子树了,再处理子树。我们在求经过一个根节点的路径时,里面还包含了点对属于同一颗子树的情况,所以要去掉这部分的点。
dis(i)+dis(j)<=k(i,j的父节点不为根节点的同一个儿子)
=dis(i)+dis(j)<=k-dis(i)+dis(j)<=k(i,j的父节点属于根节点的同一儿子).
- #include <algorithm>
- #include<stdio.h>
- #include<string.h>
- const int N=10010;
- using namespace std;
- int head[N],num,f[N],son[N],n,D,root,size,ans,dis[N],d[N],cum;
- bool vis[N];
- #define max(a,b) (a<b?b:a)
- struct edge
- {
- int st,ed,w,next;
- }e[N*2];
- void addedge(int x,int y,int w)
- {
- e[num].st=x;e[num].ed=y;e[num].w=w;e[num].next=head[x];head[x]=num++;
- e[num].st=y;e[num].ed=x;e[num].w=w;e[num].next=head[y];head[y]=num++;
- }
- void getroot(int u,int father)//求树的重心
- {
- int i,v;
- f[u]=0;son[u]=1;
- for(i=head[u];i!=-1;i=e[i].next)
- {
- v=e[i].ed;
- if(vis[v]||v==father)continue;
- getroot(v,u);
- son[u]+=son[v];
- f[u]=max(f[u],son[v]);
- }
- f[u]=max(f[u],size-son[u]);
- if(f[u]<f[root])root=u;
- }
- void getdis(int u,int father)//求节点到根节点的距离
- {
- int i,v;
- son[u]=1;//更新子树的节点的子节点数,不更新也能ac
- d[cum++]=dis[u];//将点到根节点的距离加入数组
- for(i=head[u];i!=-1;i=e[i].next)
- {
- v=e[i].ed;
- if(vis[v]||v==father)continue;
- dis[v]=dis[u]+e[i].w;
- getdis(v,u);
- son[u]+=son[v];
- }
- }
- int cont(int u,int mit)
- {
- int res=0,L,R;
- dis[u]=mit;
- cum=0;
- getdis(u,0);
- sort(d,d+cum);//将点到根节点的距离排序
- for(L=0,R=cum-1;L<R;)
- {
- if(d[L]+d[R]<=D)//如果d[L]+d[R]<=D,L代表的节点可以与(R-L)个节点成对
- res+=(R-L++);
- else R--;
- }
- return res;
- }
- void work(int u)
- {
- int i,v;
- vis[u]=true;
- ans+=cont(u,0);//路径经过该根节点的点对数
- for(i=head[u];i!=-1;i=e[i].next)
- {
- v=e[i].ed;
- if(vis[v])continue;
- ans-=cont(v,e[i].w);//减去属于v子树的点对数
- root=0;f[root]=size=son[v];
- getroot(v,0);//求v子树的根节点
- work(root);//求v子树的点对
- }
- }
- int main()
- {
- int i,x,y,w;
- while(scanf("%d%d",&n,&D),n||D)
- {
- memset(head,-1,sizeof(head));
- num=0;
- for(i=1;i<n;i++)
- {
- scanf("%d%d%d",&x,&y,&w);
- addedge(x,y,w);
- }
- memset(vis,false,sizeof(vis));
- root=0;f[root]=size=n;ans=0;
- getroot(1,0);
- work(root);
- printf("%d\n",ans);
- }
- return 0;
- }
WC2010 重建计划
This post is written in Chinese. If you have trouble to read it, please use Google Translate
问题简述
给定一棵边上带权的树,求一个平均价值最大的简单路径。路径的平均价值定义为路径的带权长度与不带权长度的比值。
问题分析
在一棵树上直接找这样的路径是很困难的,因此我们考虑将问题分解。一个基本的想法是在树上分治,为保证对数级别的时间复杂度,必须使用基于点的剖分[1]。每次找到树的重心[2]作为根节点,然后将根节点的子树平均分为两部分,两部分共用根节点。对每一部递归求解,然后把这两部分合并。求树的重心的方法是随便找一个根,求出每个子树的大小,找到max{i.child.size,N-i.size}最小的i,i就是树的重心。重心可能有多个,找一个即可。
对于一个分治的局面,每一部分都是当前根节点的一些子树组成的森林,再加上根节点,所以每一部分仍然是一棵树。最优的路径可能在某一个分治的部分中,也可能跨过根节点在两个部分中。前者可以直接递归下去求解,重点是处理后者的情况。这时需要做的一个重要转化是二分答案,由于答案的范围是已知的,我们可以在答案的范围内二分答案的值A,然后把树上每一条边的权值都减去A。判断有解的方法也就变成了判断是否存在一条带权长度大于等于0的路径,继续转化就是,判断最长的带权路径的带权长度是否大于等于0。
如何找出跨两部分的最长带权路径呢?由于路径的长度必须满足在[L,U]之间,简单的想法是在一个部分中枚举路径的一个端点的深度i,那么这条路径的另一端在另一个部分中的深度一定是在[L-i,U-i]之间。为保证路径最长,第一个部分中深度为i的一段显然应该是这个部分中深度为i的所有路径中带权长度最大的那一条,第二部分也同理,不过要枚举深度在[L-i,U-i]的最大值。如果我们确定i的枚举顺序以后,[L-i,U-i]区间的移动就是单调的,因此可以用单调队列维护最大值,因此时间复杂度就是线性的。
算法描述
求出当前树的重心,对当前树进行点剖分。
二分当前树中平均长度最大值A,判断二分范围是否满足精度,如果满足转到步骤5,否则转到步骤3。
将树上所有边权值减去A,求出剖分的两部分每个深度上的最长带权路径长度。
用单调队列维护,求出跨两部分的带权路径长度最大值,判断该值是否大于等于0,转到步骤2。
对剖分的两部分分别递归求解,如果这一部分大小大于等于L的话。
复杂度分析
对树点剖分的时间复杂度为O(logN),求重心的时间复杂度为O(N),二分答案时间复杂度为O(logV),求带权路径长度最大值时间复杂度为O(N),因此总时间复杂度为O(NlogNlogV)。
参考程序
/* * Problem: NOI Winter Camp 2010 Rebuild * Author: Guo Jiabao * Time: 2010.3.12 14:01 * Label: Solved * Memo: Binary Search + Monoqueue + Devide & Conquer on tree*/#include <cstdio>#include <cstdlib>#include <cmath>#include <cstring>#include <iostream>#include <sstream>#include <vector>#include <list>#include <deque>#include <string>#include <queue>using namespace std;#define var(a,b) typeof(b) a(b)#define foreach(a,b) for (var(a,b.begin());a!=b.end();++a)const int MAXN = 100001,MAXM=MAXN*2,INF=~0U>>1;const double LIM=1e6,FINF = 1e20;struct Monoqueue{ struct element { int key; double value; }Q[MAXN]; int head,rear; void reset() { rear = 0; head = 1; } void insert(int k,double v) { while (rear >= head && v > Q[rear].value) rear--; Q[++rear].value = v; Q[rear].key = k; } int getmax(int L) { while (Q[head].key < L) head++; return Q[head].key; }}MQ;struct edge{ edge *next; int t,c;};int N,L,U,EC,timestamp,Total;int t_ctd,t_ctd_csm,md1,md2,*t_maxdpt;int size[MAXN],depth[MAXN];double length[MAXN],d1m[MAXN],d2m[MAXN],*t_dm;edge *V[MAXN],ES[MAXM];int ava[MAXN];double Ans,t_delta;inline void addedge(int a,int b,int c){ edge *e=ES+ ++EC; e->next = V[a]; e->t = b; e->c = c; V[a] = e;}void init(){ freopen("rebuild.in","r",stdin); freopen("rebuild.out","w",stdout); scanf("%d%d%d",&N,&L,&U); for (int i=1;i<N;i++) { int a,b,c; scanf("%d%d%d",&a,&b,&c); addedge(a,b,c); addedge(b,a,c); if (L == 1 && c > Ans) Ans = c; }}void get_depth(int i){ for (edge *e=V[i];e;e=e->next) { int j = e->t; if (ava[j] != 0) continue; if (depth[j] == -1) { depth[j] = depth[i] + 1; get_depth(j); } }}int get_longest_line(int start){ int i,maxdepth; memset(depth,-1,sizeof(depth)); depth[start] = maxdepth = 0; get_depth(start); for (i=1;i<=N;i++) if (ava[i] == 0 && depth[i] > maxdepth) { maxdepth = depth[i]; start = i; } memset(depth,-1,sizeof(depth)); depth[start] = maxdepth = 0; get_depth(start); for (i=1;i<=N;i++) if (ava[i] == 0 && depth[i] > maxdepth) maxdepth = depth[i]; return maxdepth;}void get_size(int i){ int csm = 0; size[i] = 1; for (edge *e=V[i];e;e=e->next) { int j = e->t; if (ava[j] != 0) continue; if (size[j] == -1) { get_size(j); size[i] += size[j]; if (size[j] > csm) csm = size[j]; } } if (Total - size[i] > csm) csm = Total - size[i]; if (csm < t_ctd_csm) { t_ctd_csm = csm; t_ctd = i; }}int get_centroid(int i){ memset(size,-1,sizeof(size)); t_ctd_csm = INF; get_size(i); memset(size,-1,sizeof(size)); return t_ctd;}void count_size(int i){ size[i] = 1; for (edge *e=V[i];e;e=e->next) { int j = e->t; if (ava[j] != 0) continue; if (size[j] == -1) { count_size(j); size[i] += size[j]; } }}void count_depth_max_length(int i){ //求最大深度 if (depth[i] > *t_maxdpt) *t_maxdpt = depth[i]; for (edge *e=V[i];e;e=e->next) { int j = e->t; if (ava[j] != 0) continue; //求該深度最大帶權長度 if (length[i] > t_dm[depth[i]]) t_dm[depth[i]] = length[i]; if (depth[j] == -1) { length[j] = length[i] + e->c - t_delta; depth[j] = depth[i] + 1; count_depth_max_length(j); } }}bool check(double delta,int ctd,edge *f){ edge *e; int i,j,k; for (i=0;i<=N;i++) d1m[i] = d2m[i] = -FINF; t_delta = delta; memset(depth,-1,sizeof(depth)); memset(length,-1,sizeof(length)); md1 = md2 = 0; length[ctd] = depth[ctd] = 0; //統計部份1 t_dm = d1m; t_maxdpt = &md1; for (e=V[ctd];e != f;e=e->next) { int j=e->t; if (ava[j] !=0) continue; length[j] = e->c - t_delta; depth[j] = 1; count_depth_max_length(j); } //統計部份2 t_dm = d2m; t_maxdpt = &md2; for (e=f;e;e=e->next) { int j=e->t; if (ava[j] !=0) continue; length[j] = e->c - t_delta; depth[j] = 1; count_depth_max_length(j); } //單調隊列維護最大值 //確定左邊界 i = U-1; if (i > md1) i = md1; //確定右邊界 k = L-i; if (k < 1) k = 1; MQ.reset(); for (j=k;j<U-i && j<=md2 ;j++) MQ.insert(j,d2m[j]); double curv,maxv=-INF; for (;i>0 && L-i<=md2;i--) { j = U-i; if (j<=md2) MQ.insert(j,d2m[j]); int k = MQ.getmax(L-i); curv = d1m[i] + d2m[k]; if (curv > maxv) maxv = curv; } return maxv >= 0;}double binary(int ctd,edge *f){ double a=0,b=LIM,m; while (b - a >= 0.0001) { m = (a+b)/2; if (check(m,ctd,f)) a = m; else b = m; } return a;}void dct(int start){ int nowt = ++timestamp; int line = get_longest_line(start); if (line<=L || line<=2) return; int ctd = get_centroid(start); //取得重心 double cur; //分割兩部份 count_size(ctd); int pls = 0,pls2 = 0; edge *e,*f; for (e=V[ctd];e;e=e->next) { int j=e->t; if (ava[j] !=0) continue; pls += size[j]; if (pls + pls + 1 >= size[ctd] - 1) { f = e->next; pls2 = size[ctd] - pls; pls++; break; } } //合併兩部份 cur = binary(ctd,f); if (cur > Ans) Ans = cur; //遞歸部份1 int j; if (pls-1 >= L) { for (e=f;e;e=e->next) if (ava[j=e->t] ==0) ava[j] = nowt; Total = pls; dct(ctd); for (edge *e=f;e;e=e->next) if (ava[j=e->t] ==nowt) ava[j] = 0; } //遞歸部份2 if (pls2-1 >= L) { for (e=V[ctd];e!=f;e=e->next) if (ava[j=e->t] ==0) ava[j] = nowt; Total = pls2; dct(ctd); for (e=V[ctd];e!=f;e=e->next) if (ava[j=e->t] ==nowt) ava[j] = 0; }}void solve(){ memset(ava,0,sizeof(ava)); Total = N; dct(1);}int main(){ init(); solve(); printf("%.3lf\n",Ans); return 0;}
[1] 具体证明参见2009年集训队论文《分治算法在树的路径问题中的应用》。
[2] 树的重心就是满足“删除该点后,剩余的最大的子树顶点数最少”的点。
- 树的直径,树的重心,树的分冶
- 树的直径,树的重心,树的分冶
- 树的直径,树的重心,树的分冶
- 树的直径,树的重心,树的分冶
- 树的重心、直径
- 树的直径与重心
- 树的直径和重心
- UVALive 5026 (树的重心和直径)
- 树的直径/重心 学习笔记
- 树的直径,树的重心,树的分治
- 树的直径,树的重心以及树的分治
- 【原创】模板-树的前中后序遍历,树的重心直径
- 关于树:直径,重心
- 树的直径、树的重心与树的点分治
- 树的直径、树的重心与树的点分治学习笔记
- 树上方法总结 LCA 树上倍增 树链剖分 树的直径 重心
- 树的重心
- poj1655树的重心
- 将EXCEL导入到SQL,自动添加表,只可以导入03的,07的不可以
- c++宏定义和内联函数的区别
- MFC项目实战(1)文件管理器--界面设计篇
- 查看已安装python module
- AS3 正则表达式应用 格式检查与限制输入内容
- 树的直径,树的重心,树的分冶
- php开发规范
- 求出1…n之间的所有亲和数
- 怎样修改项目.NET FRamework版本
- hdu 3308 LCIS
- 解释结构模型ISM-2-3
- iOS 7: 如何为iPhone 5S编译64位应用
- java内部类详解
- Codeforces Round #204 (Div. 1) E. Jeff and Permutation