树上点对统计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 }
View Code

 

posted @ 2015-08-28 14:28  justPassBy  阅读(534)  评论(0编辑  收藏  举报