点分治(树分治)

思想

点分治就是采用分治的思想递归处理子树,用容斥去除重复的贡献。

1、找到树的重心。

2、从当前树的重心开始,加上经过该重心的贡献,减去在同一棵子树的贡献,再对每一棵子树,重新寻找树的重心,重复2.

时间复杂度:O(nlogn*计算贡献时间)

题目

1、Tree POJ - 1741

  题意:有一棵树,每条边有个权值。问树上有多少点对(u,v)满足u到v的路径上所有边的边权之和不大于k.

  思路:点分治。对于当前根,点对要么经过该根,要么不经过该根,后者也意味着该点对位于同一棵子树中,递归后又回到前者。因此,关键是计算经过该根的点对。计算子树每个点到该根的距离,排序后二分寻找符合要求的点对数目(如果dis[L]+dis[R]<=k,则有R-L对符合条件),由于之后我们会处理子树,因此,当前计算的值包括了在同一子树的点对,需要将其去掉(因为之后递归处理子树会重新计算)。

  1 #include<iostream>
  2 #include<cstdio>
  3 #include<algorithm>
  4 using namespace std;
  5 const int maxn = 10000 + 10;
  6 const int INF = 0x3f3f3f3f;
  7 struct EDGE
  8 {
  9     int from, to, w, next;
 10     EDGE(int ff = 0, int tt = 0, int ww = 0, int nn = 0) :from(ff), to(tt), w(ww), next(nn) {}
 11 }edge[maxn << 1];
 12 int Head[maxn], totedge;
 13 void addedge(int from, int to, int w)
 14 {
 15     edge[totedge] = EDGE(from, to, w, Head[from]);
 16     Head[from]=totedge++;
 17     edge[totedge] = EDGE(to, from, w, Head[to]);
 18     Head[to]=totedge++;
 19 }
 20 
 21 int root, MX;//树的重心、找到的最大子树的最小结点数目
 22 int nums[maxn];//i为根,子树的结点数量(包括自己)
 23 int mxson[maxn];//i为根,最大的子树
 24 bool vis[maxn];
 25 int n, ans,k;//输入的树的大小、结果、指定的路径长度的最大值
 26 int sn;//当前根下,总树的大小
 27 int cnt;//记录当前得到的距离数目
 28 void init()
 29 {
 30     memset(Head, -1, sizeof(Head));
 31     totedge = 0;
 32     memset(vis, 0, sizeof(vis));
 33     MX = INF;
 34     ans = 0;
 35     sn = n;
 36 }
 37 void getRoot(int u, int fa)
 38 {//找到树的重心
 39     nums[u] = 1, mxson[u] = 0;
 40     for (int i = Head[u]; i != -1; i = edge[i].next)
 41     {
 42         int v = edge[i].to;
 43         if (vis[v] || v == fa)continue;
 44         getRoot(v, u);
 45         nums[u] += nums[v];
 46         mxson[u] = max(mxson[u], nums[v]);
 47     }
 48     mxson[u] = max(mxson[u], sn - nums[u]);// n - nums[u]是经过父亲节点的子树大小
 49     if (mxson[u] < MX) root = u, MX = mxson[u];
 50 
 51 }
 52 int dis[maxn];
 53 void getdis(int u, int fa, int dist)
 54 {//找到子树所有结点到当前根的距离
 55     dis[++cnt] = dist;
 56     for (int i = Head[u]; i != -1; i = edge[i].next)
 57     {
 58         int v = edge[i].to;
 59         if (vis[v] || v == fa) continue;
 60         getdis(v, u, dist + edge[i].w);
 61     }
 62 }
 63 int solve(int r, int len)
 64 {
 65     cnt = 0;
 66     memset(dis, 0, sizeof(dis));
 67     getdis(r, 0, len);
 68     sort(dis + 1, dis + 1 + cnt);
 69     int L = 1, R = cnt;
 70     int tans = 0;
 71     while (L <= R)
 72     {
 73         if (dis[R] + dis[L] <= k)
 74         {
 75             tans +=R - L;//一共有R-L对满足条件(L,L+1)、(L,L+2)……(L,L+R)
 76             L++;
 77         }
 78         else R--;
 79     }
 80     return tans;
 81 
 82 }
 83 void pointDivide(int tr)
 84 {
 85     ans += solve(tr, 0);//求解经过tr的所有路径(点对)
 86     vis[tr] = true;//当前点标记
 87     for (int i = Head[tr]; i != -1; i = edge[i].next)
 88     {//分治处理每一棵子树
 89         int v = edge[i].to;
 90         if (vis[v]) continue;
 91         ans -= solve(v, edge[i].w);//容斥去除重复部分
 92         sn = nums[v];//重设当前总树大小,寻找新的分治点(重心)
 93         root = 0;
 94         MX = INF;
 95         getRoot(v, 0);
 96         pointDivide(root); //递归处理新的分治点
 97     }
 98 }
 99 
100 int main()
101 {
102     while (~scanf("%d%d", &n, &k)&&n!=0&&k!=0)
103     {
104         init();
105         for (int i = 1; i <= n - 1; i++)
106         {
107             int u, v, w;
108             scanf("%d%d%d", &u, &v, &w);
109             addedge(u, v, w);
110         }
111         getRoot(1, 0);
112         pointDivide(root);
113         printf("%d\n", ans);
114     }
115     return 0;
116 }
View Code

猜你喜欢

转载自www.cnblogs.com/ivan-count/p/9378539.html