POJ1741——Tree(树的点分治)
1 /* *********************************************** 2 Author :kuangbin 3 Created Time :2013-11-17 14:30:29 4 File Name :E:\2013ACM\专题学习\树的分治\POJ1741.cpp 5 ************************************************ */ 6 7 #include <stdio.h> 8 #include <string.h> 9 #include <iostream> 10 #include <algorithm> 11 #include <vector> 12 #include <queue> 13 #include <set> 14 #include <map> 15 #include <string> 16 #include <math.h> 17 #include <stdlib.h> 18 #include <time.h> 19 using namespace std; 20 const int MAXN = 10010; 21 const int INF = 0x3f3f3f3f; 22 struct Edge 23 { 24 int to,next,w; 25 }edge[MAXN*2]; 26 int head[MAXN],tot; 27 void init() 28 { 29 tot = 0; 30 memset(head,-1,sizeof(head)); 31 } 32 void addedge(int u,int v,int w) 33 { 34 edge[tot].to = v; edge[tot].w = w; 35 edge[tot].next = head[u];head[u] = tot++; 36 } 37 bool vis[MAXN]; 38 int size[MAXN],dep[MAXN]; 39 int le,ri; 40 int dfssize(int u,int pre) 41 { 42 size[u] = 1; 43 for(int i = head[u];i != -1;i = edge[i].next) 44 { 45 int v = edge[i].to; 46 if(v == pre || vis[v])continue; 47 size[u] += dfssize(v,u); 48 } 49 return size[u]; 50 } 51 int minn; 52 //找重心 53 void getroot(int u,int pre,int totnum,int &root) 54 { 55 int maxx = totnum - size[u]; 56 for(int i = head[u];i != -1;i = edge[i].next) 57 { 58 int v = edge[i].to; 59 if(v == pre || vis[v])continue; 60 getroot(v,u,totnum,root); 61 maxx = max(maxx,size[v]); 62 } 63 if(maxx < minn){minn = maxx; root = u;} 64 } 65 void dfsdepth(int u,int pre,int d) 66 { 67 dep[ri++] = d; 68 for(int i = head[u];i != -1;i = edge[i].next) 69 { 70 int v = edge[i].to; 71 if(v == pre || vis[v])continue; 72 dfsdepth(v,u,d+edge[i].w); 73 } 74 } 75 int k; 76 int getdep(int a,int b) 77 { 78 sort(dep+a,dep+b); 79 int ret = 0, e = b-1; 80 for(int i = a;i < b;i++) 81 { 82 if(dep[i] > k)break; 83 while(e >= a && dep[e] + dep[i] > k)e--; 84 ret += e - a + 1; 85 if(e > i)ret--; 86 } 87 return ret>>1; 88 } 89 int solve(int u) 90 { 91 int totnum = dfssize(u,-1); 92 int ret = 0; 93 minn = INF; 94 int root; 95 getroot(u,-1,totnum,root); 96 vis[root] = true; 97 for(int i = head[root];i != -1;i = edge[i].next) 98 { 99 int v = edge[i].to; 100 if(vis[v])continue; 101 ret += solve(v); 102 } 103 le = ri = 0; 104 for(int i = head[root];i != -1;i = edge[i].next) 105 { 106 int v = edge[i].to; 107 if(vis[v])continue; 108 dfsdepth(v,root,edge[i].w); 109 ret -= getdep(le,ri); 110 le = ri; 111 } 112 ret += getdep(0,ri); 113 for(int i = 0;i < ri;i++) 114 { 115 if(dep[i] <= k)ret++; 116 else break; 117 } 118 vis[root] = false; 119 return ret; 120 } 121 122 int main() 123 { 124 //freopen("in.txt","r",stdin); 125 //freopen("out.txt","w",stdout); 126 int n; 127 int u,v,w; 128 while(scanf("%d%d",&n,&k) == 2) 129 { 130 if(n == 0 && k == 0)break; 131 init(); 132 for(int i = 1;i < n;i++) 133 { 134 scanf("%d%d%d",&u,&v,&w); 135 addedge(u,v,w); 136 addedge(v,u,w); 137 } 138 memset(vis,false,sizeof(vis)); 139 printf("%d\n",solve(1)); 140 } 141 return 0; 142 }
树的分治算法——基于点的分治
首先选取一个点将无根树转为有根树,再递归处理每一颗以根结点的儿子为根的子树。
给定一颗N(1<=N<=10000)个结点的带权树,定义dist(u,v)为u,v两点间的最短路径长度,路径的长度定义为路径上所有边的权和。
再给定一个K(1<=K<=10^9),如果对于不同的两个结点a,b,如果满足dist(a,b)<=K,则称(a,b)为合法点对。
求合法点对个数。
算法分析:
如果使用普通的DFS遍历,时间复杂度高达O(N^2),而使用时间复杂度为O(NK)的动态规划,更是无法在规定时限内出解的。
我们知道一条路径要么过根结点,要么在一颗子树中,这启发了我们可以使用分治算法。
路径在子树中的情况只需递归处理即可,下面我们来分析如何处理路径过根结点的情况。
记Depth(i)表示点i到根结点的路径长度,Belong(i)=X(X为根结点的某个儿子,且结点i在以X为根的子树内)。那么我们要统计的就是:
满足Depth(i)+Depth(j)<=K且Belong(i)!=Belong(j)的(i,j)个数
=满足Depth(i)+Depth(j)<=K的(i,j)个数
- 满足Depth(i)+Depth(j)<=K的(i,j)个数且Belong(i)==Belong(j)的(i,j)个数
而对于这两个部分,都是要求出满足Ai+Aj<=K的(i,j)的对数。将A排序后利用单调性可以得出一个O(N)的算法,所以可以用O(NlogN)的时间来解决这个问题。
综上,此题使用树的点分治算法时间复杂度为O(Nlog2N)。
所谓点分治,对于一条树路径,只有经过或不经过一个点的情况。
对于不经过的情况 把一棵树按这个点拆成好几棵分治就行了。
(楼天城男人八题,漆子超IOI2009国家集训队论文)