树上点对统计poj1741(树的点分治)
给定一棵树,边上有权值,要统计有多少对点路径的权值和<=k
分治算法在树的路径中的应用 这个论文里面有分析。
任意两点的路径,要么过根结点,要么在子树中。如果在子树中,那么只要递归处理就行了。
所以只要统计过根结点的对数。
所以只要算出任意结点到根结点的距离,用dist数组存下来,然后排序dist数组,就可以在O(n)的时间内算出有多少对点满足条件,具体见counts函数
但是counter函数计算时,会把路径都在子树中的情况也给加进来,所以需要再对相应的子树进行counts函数,然后减去。
然后在递归计算路径经过孩子结点的情况。
但是这样子的算法是会退化的,因为树可能是一条链。
所以就需要用到重心了。 因为重心具有的一个性质是删掉重心后,可以使得剩下的分支中,最大的最小。
所以在算出所有经过u的点对之后,递归计算v这个子树时,相当于计算一棵全新的树,所以只要找到这棵树的重心,继续计算就行了。
因为结点数可以每次都减少为原来的一般,所以层数只有logn层, 每层统计时,排序要nlogn,所以总的时间复杂度是n*logn*logn
1 #pragma warning(disable:4996) 2 #pragma comment(linker, "/STACK:1024000000,1024000000") 3 #include <stdio.h> 4 #include <string.h> 5 #include <time.h> 6 #include <math.h> 7 #include <map> 8 #include <set> 9 #include <queue> 10 #include <stack> 11 #include <vector> 12 #include <bitset> 13 #include <algorithm> 14 #include <iostream> 15 #include <string> 16 #include <functional> 17 const int INF = 1 << 30; 18 typedef __int64 LL; 19 /* 20 dist(u,v)为u,v两点路径上所有边的权和 21 如果dist(u,v)<=k,那么称(a,b)为合法点对 22 求合法点对的个数 23 N<=10000, k<=10^9 24 25 depth(i) + depth(j) <=k 且belong(i)!=belong(j) 26 27 我的问题是? 分治的时候,如何将树两边的点合起来??? 28 */ 29 const int N = 20000 + 10; 30 struct Node 31 { 32 int to, next, dis; 33 }g[N]; 34 int head[N], e; 35 int n, k, ans; 36 int size[N]; 37 int mins, total, root; 38 int dist[N], p; 39 int vis[N]; 40 int cnt[N]; 41 void addEdge(int u, int v, int dis) 42 { 43 g[e].to = v; 44 g[e].dis = dis; 45 g[e].next = head[u]; 46 head[u] = e++; 47 } 48 49 void dfsRoot(int u, int f) 50 { 51 size[u] = 1; 52 int mxs = 0; 53 for (int i = head[u]; i != -1; i = g[i].next) 54 { 55 int v = g[i].to; 56 if (v == f || vis[v]) continue; 57 dfsRoot(v, u); 58 size[u] += size[v]; 59 mxs = std::max(mxs, size[v]); 60 } 61 //下面是找重心的代码 62 if(mxs < total - size[u]) 63 mxs = total - size[u]; 64 if (mxs < mins) 65 mins = mxs, root = u; 66 } 67 68 void getDis(int u, int f, int dis) 69 { 70 dist[p++] = dis;//遍历没有访问过的点,将到根结点的距离存下来 71 for (int i = head[u]; i != -1; i = g[i].next) 72 { 73 int v = g[i].to; 74 if (v == f || vis[v]) continue; 75 getDis(v, u, dis + g[i].dis); 76 } 77 } 78 int counts(int u, int dis) 79 { 80 int ret = 0; 81 p = 0; 82 getDis(u, 0, dis);//得到dist数组 83 std::sort(dist, dist + p);//每访问一个重心都要排序一次,所以是n*logn*logn 84 int l = 0, r = p - 1; 85 while (l < r) 86 { 87 //如果dist[l]+dist[r]<=k ,那么dist[l]+任意的点都是可行的 88 if (dist[l] + dist[r] <= k) 89 { 90 ret += (r - l); 91 l++;//判断下一个l 92 } 93 else//如果这个r不行,那么这个r与谁结合都是不行的 94 r--; 95 } 96 return ret; 97 } 98 99 void go(int u) 100 { 101 //total存的是子树所有结点 102 mins = INF, total = size[u] ? size[u] : n; 103 dfsRoot(u, -1);//找到重心 104 u = root; 105 vis[u] = true; 106 cnt[u] = 0; 107 cnt[u] += counts(u, 0);//统计过这个点的点对 108 //但是会发生一个错误就是可能统计了位于同一棵子树的点 109 for (int i = head[u]; i != -1; i = g[i].next) 110 { 111 int v = g[i].to; 112 if (vis[v]) continue; 113 cnt[u] -= counts(v, g[i].dis);//所有要减去位于同一棵子树的点 114 go(v); 115 } 116 } 117 int main() 118 { 119 120 while(scanf("%d%d", &n, &k),n+k) 121 { 122 int u, v, dis; 123 e = 0; 124 memset(head, -1, sizeof(head)); 125 for (int i = 1;i < n;++i) 126 { 127 scanf("%d%d%d", &u, &v, &dis); 128 addEdge(u, v, dis); addEdge(v, u, dis); 129 } 130 ans = 0; 131 size[1] = 0; 132 memset(vis, 0, sizeof(vis)); 133 go(1); 134 for (int i = 1;i <= n;++i) 135 ans += cnt[i]; 136 printf("%d\n", ans); 137 } 138 return 0; 139 }