题意:给定一棵带权无向树,并给定一个整数m。求树上点间最短路不大于m的点对数量。(1987题意完全相同,又重新写一遍熟练一下)
思路:比较明显的思路是用LCA来做,但问题是用LCA的话,当前问题的查询数量级为n^2,所以不管使用离线LCA还是在线LCA都逃不过O(n^2)的复杂度。经过学习知道了树分治这种东西。
将无根树转化成有根树进行观察。满足条件的点对有两种情况:两个点的路径横跨树根,两个点位于同一颗子树中。
如果我们已经知道了此时所有点到根的距离a[i],a[x] + a[y] <= k的(x, y)对数就是结果,这个可以通过排序之后O(n)的复杂度求出。然后根据分治的思想,分别对所有的儿子求一遍即可,但是这会出现重复的——当前情况下两个点位于一颗子树中,那么应该将其减掉(显然这两个点是满足题意的,为什么减掉呢?因为在对子树进行求解的时候,会重新计算)。
在进行分治时,为了避免树退化成一条链而导致时间复杂度变为O(N^2),每次都找树的重心,这样,所有的子树规模就会变的很小了。时间复杂度O(Nlog^2N)。
树的重心的算法可以线性求解,关于树的重心的定义和解法可以参见poj1655。
#include <stdio.h> #include <string.h> #include <stdlib.h> #define clr(s,t) memset(s,t,sizeof(s)) #define max(a,b) ((a)>(b)?(a):(b)) #define min(a,b) ((a)<(b)?(a):(b)) #define N 10005 struct edge{ int y,w,next; }e[N<<1]; int n,m,top,res,root,len,size; int first[N],used[N],son[N],rnum[N],dis[N],q[N]; void init(){ top = res = root = 0; clr(first, -1); clr(used, 0); clr(son, 0); clr(rnum, 0); rnum[0] = N; } int cmp(const void* a,const void* b){ return (*(int*)a) - (*(int*)b); } void add(int x,int y,int w){ e[top].y = y; e[top].w = w; e[top].next = first[x]; first[x] = top++; } void findroot(int x,int fa){ int i,y; son[x] = 1; rnum[x] = 0; for(i = first[x];i!=-1;i=e[i].next){ y = e[i].y; if(y!=fa && !used[y]){ findroot(y, x); son[x] += son[y]; rnum[x] = max(rnum[x], son[y]); } } rnum[x] = max(rnum[x], size-son[x]); if (rnum[x] < rnum[root]) root = x; } void getdis(int x,int fa){ int i; q[len++] = dis[x]; son[x] = 1; for(i = first[x];i!=-1;i=e[i].next) if(e[i].y != fa &&!used[e[i].y]){ dis[e[i].y] = dis[x]+e[i].w; getdis(e[i].y, x); son[x] += son[e[i].y]; } } int update(int len){ int i,j,sum=0; qsort(q, len, sizeof(int), cmp); for(i = 0,j = len-1;;i++){ while(q[i]+q[j]>m) j--; if(i>=j) break; sum += j-i; } return sum; } void tdc(int x){//tree_divide_conquer int i,y; findroot(x,root=0); len = 0; dis[root] = 0; getdis(root,0); res += update(len); used[root] = 1; for(i = first[root];i!=-1;i=e[i].next) if(!used[y=e[i].y]){ len = 0; dis[y] = e[i].w; getdis(y,root); res -= update(len); size = son[y]; tdc(y); } } int main(){ while(scanf("%d %d",&n,&m) && (n+m)){ int i,a,b,w; init(); for(i = 1;i<n;i++){ scanf("%d %d %d",&a,&b,&w); add(a,b,w);add(b,a,w); } size = n; tdc(1); printf("%d\n",res); } return 0; }
1987:
#include <stdio.h> #include <string.h> #include <stdlib.h> #define clr(s,t) memset(s,t,sizeof(s)) #define max(a,b) ((a)>(b)?(a):(b)) #define min(a,b) ((a)<(b)?(a):(b)) #define N 40005 struct edge{ int y,w,next; }e[N<<1]; int first[N],used[N],son[N],tnum[N],q[N],dis[N]; int top,n,m,th,res,len,size,root; void init(){ top = res = len = 0; clr(first, -1); clr(used, 0); tnum[0] = N; } void add(int x,int y,int w){ e[top].y = y; e[top].w = w; e[top].next = first[x]; first[x] = top++; } int cmp(const void* a,const void* b){ return (*(int*)a) - (*(int*)b); } void getroot(int x,int fa){ int i,y; son[x] = 1; tnum[x] = 0; for(i = first[x];i!=-1;i=e[i].next){ y = e[i].y; if(!used[y] && y!=fa){ getroot(y,x); son[x] += son[y]; tnum[x] = max(tnum[x], son[y]); } } tnum[x] = max(tnum[x], size-son[x]); if(tnum[x] < tnum[root]) root = x; } void getdis(int x,int fa){ int i,y; q[len++] = dis[x]; son[x] = 1; for(i = first[x];i!=-1;i=e[i].next){ y = e[i].y; if(!used[y] && y!=fa){ dis[y] = dis[x]+e[i].w; getdis(y,x); son[x] += son[y]; } } } int updatedis(){ int i,j,sum = 0; qsort(q, len, sizeof(int), cmp); for(i = 0,j = len-1;;i++){ while(q[i]+q[j] > th) j--; if(i>=j) break; sum += j-i; } return sum; } void tdc(int x){ int i,y; getroot(x,root = 0); dis[root] = len = 0; getdis(root,0); res += updatedis(); used[root] = 1; for(i = first[root];i!=-1;i=e[i].next){ y = e[i].y; if(!used[y]){ dis[y] = e[i].w; len = 0; getdis(y, root); res -= updatedis(); size = son[y]; tdc(y); } } } int main(){ int i,a,b,c; char ch; init(); scanf("%d %d",&n,&m); for(i = 0;i<m;i++){ scanf("%d %d %d %c",&a,&b,&c,&ch); add(a,b,c); add(b,a,c); } scanf("%d",&th); size = n; tdc(1); printf("%d\n",res); return 0; }