思想
点分治就是采用分治的思想递归处理子树,用容斥去除重复的贡献。
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 }