思想
点分治就是采用分治的思想递归处理子树,用容斥去除重复的贡献。
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 ", ans); 114 } 115 return 0; 116 }
2、聪聪可可 HYSBZ - 2152
题意:求树上点对之间距离为3的倍数的个数。(u,v)与(v,u)算不同点对。
思路:点分治,每次计算子树各个结点到根的距离%3,统计%3结果为0、1、2的数目,则符合条件的点对数目为dis[0]*(dis[0]-1)+dis[0]+dis[1]*dis[2]*2。
1 #include<iostream> 2 #include<cstdio> 3 #include<algorithm> 4 #include<cstring> 5 using namespace std; 6 const int maxn = 20000 + 10; 7 const int INF = 0x3f3f3f3f; 8 struct EDGE 9 { 10 int from, to, w, next; 11 EDGE(int ff = 0, int tt = 0, int ww = 0, int nn = 0) :from(ff), to(tt), w(ww), next(nn) {} 12 }edge[maxn << 1]; 13 int Head[maxn], totedge; 14 int gcd(int x, int y) 15 { 16 if (x < y) swap(x, y); 17 while (y) 18 { 19 int tmp = x % y; 20 x = y; 21 y = tmp; 22 } 23 return x; 24 } 25 void addedge(int from, int to, int w) 26 { 27 edge[totedge] = EDGE(from, to, w, Head[from]); 28 Head[from] = totedge++; 29 edge[totedge] = EDGE(to, from, w, Head[to]); 30 Head[to] = totedge++; 31 } 32 33 int root, MX;//树的重心、找到的最大子树的最小结点数目 34 int nums[maxn];//i为根,子树的结点数量(包括自己) 35 int mxson[maxn];//i为根,最大的子树 36 bool vis[maxn]; 37 int n, ans, k;//输入的树的大小、结果、指定的路径长度的最大值 38 int sn;//当前根下,总树的大小 39 void init() 40 { 41 memset(Head, -1, sizeof(Head)); 42 totedge = 0; 43 memset(vis, 0, sizeof(vis)); 44 MX = INF; 45 ans = 0; 46 sn = n; 47 } 48 void getRoot(int u, int fa) 49 {//找到树的重心 50 nums[u] = 1, mxson[u] = 0; 51 for (int i = Head[u]; i != -1; i = edge[i].next) 52 { 53 int v = edge[i].to; 54 if (vis[v] || v == fa)continue; 55 getRoot(v, u); 56 nums[u] += nums[v]; 57 mxson[u] = max(mxson[u], nums[v]); 58 } 59 mxson[u] = max(mxson[u], sn - nums[u]);// n - nums[u]是经过父亲节点的子树大小 60 if (mxson[u] < MX) root = u, MX = mxson[u]; 61 62 } 63 int dis[3]; 64 void getdis(int u, int fa, int dist) 65 {//找到子树所有结点到当前根的距离 66 dis[dist]++; 67 for (int i = Head[u]; i != -1; i = edge[i].next) 68 { 69 int v = edge[i].to; 70 if (vis[v] || v == fa) continue; 71 getdis(v, u, (dist + edge[i].w)%3); 72 } 73 } 74 int solve(int r, int len) 75 { 76 memset(dis, 0, sizeof(dis)); 77 getdis(r, 0, len%3); 78 return dis[0] * (dis[0]-1)+dis[0] + dis[1] * dis[2] * 2; 79 } 80 void pointDivide(int tr) 81 { 82 ans += solve(tr, 0);//求解经过tr的所有路径(点对) 83 vis[tr] = true;//当前点标记 84 for (int i = Head[tr]; i != -1; i = edge[i].next) 85 {//分治处理每一棵子树 86 int v = edge[i].to; 87 if (vis[v]) continue; 88 ans -= solve(v, edge[i].w);//容斥去除重复部分 89 sn = nums[v];//重设当前总树大小,寻找新的分治点(重心) 90 root = 0; 91 MX = INF; 92 getRoot(v, 0); 93 pointDivide(root); //递归处理新的分治点 94 } 95 } 96 97 int main() 98 { 99 while (~scanf("%d", &n)) 100 { 101 init(); 102 for (int i = 1; i <= n - 1; i++) 103 { 104 int u, v, w; 105 scanf("%d%d%d", &u, &v, &w); 106 addedge(u, v, w); 107 } 108 getRoot(1, 0); 109 pointDivide(root); 110 int gg = gcd(n*n, ans); 111 112 printf("%d/%d ", ans/gg,n*n/gg); 113 } 114 return 0; 115 }
3、Happy King HDU - 5314
题意:树上每个结点有个权值。求树上有多少点对两两之间的路径上的最大点权与最小点权之差不超过D。
思路:点分治。每次计算子树各个结点到根路径上的最大点权与最小点权,当之差不超过D时记录进数组DT。按照最小点权排序。对于i,在(DT,DT+i)中找到第一个最小点权大于等于max_i-D的位置j,则i与j~i-1两两组合形成的点对符合要求。由于按照min_i从小到大排序,那么min_j>=max_i-D->max_i-min_j<=D,j+1~i-1之间z满足min_j<=min_z<=min_i,自然max_i-min_z<=D。对于j~i之间的z的max_z,由于max_z-min_z<=D,设m在z+1~i之间,则min_m>=min_z,故max_z-min_m<=D。因此,此间的i-j个点对都符合要求。由于(u,v)与(v,u)为不同点对,故最后答案*2即可。
1 #include<iostream> 2 #include<cstdio> 3 #include<algorithm> 4 #include<cstring> 5 using namespace std; 6 const int maxn = 100000 + 10; 7 const int INF = 0x3f3f3f3f; 8 struct EDGE 9 { 10 int from, to, next; 11 EDGE(int ff = 0, int tt = 0, int nn = 0) :from(ff), to(tt),next(nn) {} 12 }edge[maxn << 1]; 13 int Head[maxn], totedge; 14 int gcd(int x, int y) 15 { 16 if (x < y) swap(x, y); 17 while (y) 18 { 19 int tmp = x % y; 20 x = y; 21 y = tmp; 22 } 23 return x; 24 } 25 void addedge(int from, int to) 26 { 27 edge[totedge] = EDGE(from, to, Head[from]); 28 Head[from] = totedge++; 29 edge[totedge] = EDGE(to, from, Head[to]); 30 Head[to] = totedge++; 31 } 32 33 int root, MX;//树的重心、找到的最大子树的最小结点数目 34 int val[maxn]; 35 int nums[maxn];//i为根,子树的结点数量(包括自己) 36 int mxson[maxn];//i为根,最大的子树 37 bool vis[maxn]; 38 int n,D; 39 long long ans; 40 int sn;//当前根下,总树的大小 41 void init() 42 { 43 memset(Head, -1, sizeof(Head)); 44 totedge = 0; 45 memset(vis, 0, sizeof(vis)); 46 MX = INF; 47 ans = 0; 48 sn = n; 49 } 50 void getRoot(int u, int fa) 51 {//找到树的重心 52 nums[u] = 1, mxson[u] = 0; 53 for (int i = Head[u]; i != -1; i = edge[i].next) 54 { 55 int v = edge[i].to; 56 if (vis[v] || v == fa)continue; 57 getRoot(v, u); 58 nums[u] += nums[v]; 59 mxson[u] = max(mxson[u], nums[v]); 60 } 61 mxson[u] = max(mxson[u], sn - nums[u]);// n - nums[u]是经过父亲节点的子树大小 62 if (mxson[u] < MX) root = u, MX = mxson[u]; 63 64 } 65 66 struct info 67 { 68 int maxv, minv; 69 info(int mx = 0, int mn = 0) :maxv(mx), minv(mn){} 70 friend bool operator <(const info&i1, const info&i2) 71 { 72 if (i1.minv == i2.minv) return i1.maxv < i2.maxv; 73 else return i1.minv < i2.minv; 74 } 75 }DT[maxn]; 76 int cnt; 77 void get_mx_mn(int u, int fa, int mx,int mn) 78 {//找到子树所有结点到当前根的路径上结点值的最大值和最小值 79 mx = max(mx, val[u]), mn = min(mn, val[u]); 80 if (mx - mn <= D) DT[cnt++] = info(mx, mn); 81 for (int i = Head[u]; i != -1; i = edge[i].next) 82 { 83 int v = edge[i].to; 84 if (vis[v] || v == fa) continue; 85 get_mx_mn(v, u,mx,mn); 86 } 87 } 88 long long solve(int r, int len) 89 { 90 cnt = 0; 91 get_mx_mn(r, 0,len,len); 92 sort(DT, DT + cnt); 93 long long tans = 0; 94 for (int i = 0; i < cnt; i++) 95 { 96 int tmin = DT[i].maxv - D; 97 int pos = lower_bound(DT, DT + i, info(0, tmin)) - DT; 98 tans += i - pos; 99 } 100 return tans; 101 } 102 void pointDivide(int tr) 103 { 104 ans += solve(tr, val[tr]);//求解经过tr的所有路径(点对) 105 vis[tr] = true;//当前点标记 106 for (int i = Head[tr]; i != -1; i = edge[i].next) 107 {//分治处理每一棵子树 108 int v = edge[i].to; 109 if (vis[v]) continue; 110 ans -= solve(v,val[tr]);//容斥去除重复部分 111 sn = nums[v];//重设当前总树大小,寻找新的分治点(重心) 112 root = 0; 113 MX = INF; 114 getRoot(v, 0); 115 pointDivide(root); //递归处理新的分治点 116 } 117 } 118 119 int main() 120 { 121 int t; 122 scanf("%d", &t); 123 while (t--) 124 { 125 scanf("%d%d", &n, &D); 126 init(); 127 for (int i = 1; i <= n; i++) scanf("%d", &val[i]); 128 int mxv = -INF,mnv=INF; 129 for (int i = 1; i <= n - 1; i++) 130 { 131 int u, v; 132 scanf("%d%d", &u, &v); 133 addedge(u, v); 134 } 135 getRoot(1, 0); 136 pointDivide(root); 137 printf("%lld ", ans*2); 138 } 139 return 0; 140 }
4、Garden of Eden HDU - 5977
题意(等效):有一棵树,每个结点有一种颜色,问有多少点对使得之间的路径覆盖所有种类的颜色。颜色种类为1~10.
思路:点分治,用二进制记录子树的结点到根的路径的颜色状态,因为如果一条路径状态为11,那么其也符合01、10、00的状态。进行状态转移后,得到每种状态的路径数,然后枚举子树各个结点的状态cur,累计Count[allstates^cur]。
1 #include<iostream> 2 #include<cstdio> 3 #include<algorithm> 4 #include<cstring> 5 using namespace std; 6 const int maxn = 50000 + 10; 7 const int INF = 0x3f3f3f3f; 8 const int totstate = 1 << 12; 9 int type[15]; 10 struct EDGE 11 { 12 int from, to, next; 13 EDGE(int ff = 0, int tt = 0, int nn = 0) :from(ff), to(tt), next(nn) {} 14 }edge[maxn << 1]; 15 int Head[maxn], totedge; 16 17 void addedge(int from, int to) 18 { 19 edge[totedge] = EDGE(from, to, Head[from]); 20 Head[from] = totedge++; 21 edge[totedge] = EDGE(to, from, Head[to]); 22 Head[to] = totedge++; 23 } 24 25 int root, MX;//树的重心、找到的最大子树的最小结点数目 26 int val[maxn]; 27 int nums[maxn];//i为根,子树的结点数量(包括自己) 28 int mxson[maxn];//i为根,最大的子树 29 bool vis[maxn]; 30 int n, k; 31 long long ans; 32 int sn;//当前根下,总树的大小 33 void init() 34 { 35 memset(Head, -1, sizeof(Head)); 36 totedge = 0; 37 memset(vis, 0, sizeof(vis)); 38 MX = INF; 39 ans = 0; 40 sn = n; 41 } 42 void getRoot(int u, int fa) 43 {//找到树的重心 44 nums[u] = 1, mxson[u] = 0; 45 for (int i = Head[u]; i != -1; i = edge[i].next) 46 { 47 int v = edge[i].to; 48 if (vis[v] || v == fa)continue; 49 getRoot(v, u); 50 nums[u] += nums[v]; 51 mxson[u] = max(mxson[u], nums[v]); 52 } 53 mxson[u] = max(mxson[u], sn - nums[u]);// n - nums[u]是经过父亲节点的子树大小 54 if (mxson[u] < MX) root = u, MX = mxson[u]; 55 56 } 57 int st[maxn],Count[totstate]; 58 int cnt; 59 void get_type_state(int u, int fa, int tp) 60 {//找到子树所有结点到当前根的路径上结点值的并的总状态 61 tp = tp | val[u]; 62 st[cnt++] = tp; 63 Count[tp]++; 64 for (int i = Head[u]; i != -1; i = edge[i].next) 65 { 66 int v = edge[i].to; 67 if (vis[v] || v == fa) continue; 68 get_type_state(v, u,tp); 69 } 70 } 71 long long solve(int r, int tp) 72 { 73 cnt = 0; 74 memset(Count, 0, sizeof(Count)); 75 get_type_state(r, 0, tp); 76 int allstates = (1 << k) - 1; 77 for (int i = 1; i <= k; i++) 78 {//得到路径各个状态的数目 79 for (int j = allstates; j >= 0; j--) 80 { 81 if (!(type[i] & j)) Count[j] += Count[j | type[i]];//如果某个结点到根有1、2种苹果,则其也满足有第1种苹果的状态以及只有第2种苹果的状态 82 } 83 } 84 long long tans = 0; 85 for (int i = 0; i < cnt; i++)tans += Count[allstates^st[i]]; 86 return tans; 87 } 88 void pointDivide(int tr) 89 { 90 ans += solve(tr, val[tr]);//求解经过tr的所有路径(点对) 91 vis[tr] = true;//当前点标记 92 for (int i = Head[tr]; i != -1; i = edge[i].next) 93 {//分治处理每一棵子树 94 int v = edge[i].to; 95 if (vis[v]) continue; 96 ans -= solve(v, val[tr]);//容斥去除重复部分 97 sn = nums[v];//重设当前总树大小,寻找新的分治点(重心) 98 root = 0; 99 MX = INF; 100 getRoot(v, 0); 101 pointDivide(root); //递归处理新的分治点 102 } 103 } 104 105 int main() 106 { 107 type[1] = 1; 108 for (int i = 2; i <= 10; i++) type[i] = type[i - 1] << 1; 109 while (~scanf("%d%d",&n,&k)) 110 { 111 init(); 112 for (int i = 1; i <= n; i++) 113 { 114 int v; 115 scanf("%d", &v); 116 val[i] = type[v]; 117 } 118 for (int i = 1; i <= n - 1; i++) 119 { 120 int u, v; 121 scanf("%d%d", &u, &v); 122 addedge(u, v); 123 } 124 getRoot(1, 0); 125 pointDivide(root); 126 printf("%lld ", ans); 127 } 128 return 0; 129 }
5、震波 HYSBZ - 3730
题意:有一棵树,点权。两种操作:询问距离u不超过D的所有结点的权值和;修改结点u的权值为v。
思路:动态点分治、线段树、在线LCA。
对于每个重心我们开两颗线段树,维护区间和:
第一棵,对以重心rt为根的子树的结点v,以距离重心rt的距离为下标(线段树区间下标),点权为值插入线段树。
第二棵,对以重心rt为根的子树的结点v,以距离重心rt的父亲的距离为下标(线段树区间下标),点权为值插入线段树。
同时记录每个重心的前重心,用以接下来的查询和更新操作。
LCA用来查询树上两点距离。
修改时,可以看成是对u结点加上“rem=v-原值”。然后对含有其的每棵线段树的sum值进行更新。对重心i,对其前重心pre的第一棵线段树在位置dis(i,pre)加上rem,对i的第二棵线段树在位置dis(i,pre)加上rem。
查询时,对当前重心i的第一棵线段树求得[0,D]区间和。然后不断向上更新(爬树高)。对重心i,加上其前重心pre的第一棵线段树在区间[0,D-dis[i,pre]]的权值和,减去i的第二棵线段树在区间[0,D-dis[i,pre]]的权值和。原理是容斥,因为对每棵子树的重心我们都有累计值,但由于在同一子树的值会累计重复(因为其前重心和当前重心我们都累计了,而累计前重心时,同时也把在当前重心的点也统计进去),故需要减去,这就是为什么每个重心要设立两棵线段树的原因。
1 #include <cstdio> 2 #include<cstring> 3 using namespace std; 4 const int maxn = 500100; 5 inline void swap(int &x, int &y) { 6 static int tmp; 7 tmp = x; 8 x = y; 9 y = tmp; 10 } 11 inline int Max(const int &a, const int &b) 12 { 13 return a > b ? a : b; 14 } 15 /*快读快写*/ 16 inline char get(void) 17 { 18 static char buf[1000000], *p1 = buf, *p2 = buf; 19 if (p1 == p2) { 20 p2 = (p1 = buf) + fread(buf, 1, 1000000, stdin); 21 if (p1 == p2) return EOF; 22 } 23 return *p1++; 24 } 25 inline void read(int &x) 26 { 27 x = 0; static char c; bool minus = false; 28 for (; !(c >= '0' && c <= '9'); c = get()) if (c == '-') minus = true; 29 for (; c >= '0' && c <= '9'; x = x * 10 + c - '0', c = get()); if (minus) x = -x; 30 } 31 inline void write(int x) 32 { 33 if (!x) return (void)puts("0"); 34 if (x < 0) putchar('-'), x = -x; 35 static short s[12], t; 36 while (x) s[++t] = x % 10, x /= 10; 37 while (t) putchar('0' + s[t--]); 38 putchar(' '); 39 } 40 int n, m, ans; 41 int Head[maxn], totedge; 42 struct Edge 43 { 44 int to, next; 45 Edge(void) {} 46 Edge(const int &to, const int &nxt) : to(to), next(nxt) {} 47 } edge[maxn << 1]; 48 49 inline void add(int from, int to) 50 { 51 edge[totedge] = Edge(to, Head[from]), Head[from] = totedge++; 52 edge[totedge] = Edge(from, Head[to]), Head[to] = totedge++; 53 } 54 /*LCA*/ 55 int dir[maxn], dp[maxn][30]; 56 inline void lca_dfs(int u, int fa) 57 { 58 dp[u][0] = fa; 59 for (int i = 1; i <= 20; i++) 60 { 61 dp[u][i] = dp[dp[u][i - 1]][i - 1]; 62 } 63 for (int i = Head[u], v; i!=-1; i = edge[i].next) 64 { 65 v = edge[i].to; 66 if (v == fa) continue; 67 dir[v] = dir[u] + 1; 68 lca_dfs(v, u); 69 } 70 } 71 inline int LCA(int x, int y) 72 { 73 if (dir[x] < dir[y]) swap(x, y); 74 int tmp = dir[x] - dir[y]; 75 for (int k = 0, j = 1; j <= tmp; j <<= 1, k++) 76 if (tmp & j) x = dp[x][k]; 77 while (x != y) 78 { 79 int j = 0; 80 while (dp[x][j] != dp[y][j]) j++; 81 if (j) j--; 82 x = dp[x][j], y = dp[y][j]; 83 } 84 return x; 85 } 86 inline int dist(int x, int y) 87 { 88 return dir[x] + dir[y] - (dir[LCA(x, y)] << 1); 89 } 90 /*LCA结束*/ 91 int sum[maxn << 4], lson[maxn << 4], rson[maxn << 4]; 92 int sz, id_root[maxn][2];//分配线段树结点、记录所有点分树的根 93 /* 94 对于每个重心我们开两颗线段树: 95 第一棵,对以重心rt为根的子树的结点v,以距离重心rt的距离为下标(线段树区间下标),点权为值插入线段树。调用函 96 第二棵,对以重心rt为根的子树的结点v,以距离重心rt的父亲的距离为下标(线段树区间下标),点权为值插入线段树。 97 */ 98 inline void insert(int &rt, int l, int r, int x, int v) 99 {//在x的位置加上v 100 if (!rt) rt = ++sz; sum[rt] += v; 101 if (l == r) return; 102 int mid = (l + r) >> 1; 103 if (mid >= x) insert(lson[rt], l, mid, x, v); 104 else insert(rson[rt], mid + 1, r, x, v); 105 } 106 inline int query(int rt, int l, int r, int L, int R) 107 {//询问[L,R]之间的权值和 108 if (!rt) return 0; 109 if (l >= L && r <= R) return sum[rt]; 110 int mid = (l + r) >> 1; 111 if (R <= mid) return query(lson[rt], l, mid, L, R); 112 else if (L > mid) return query(rson[rt], mid + 1, r, L, R); 113 else return query(lson[rt], l, mid, L, mid) + query(rson[rt], mid + 1, r, mid + 1, R); 114 } 115 bool vis[maxn]; 116 int d[maxn];//距离 117 int val[maxn];//结点值 118 int sn;//当前树的总节点数 119 int nums[maxn];//子树的节点数 120 int root, mxson[maxn], pre[maxn];//树的重心、含有最多结点的子树的数目、前重心 121 122 inline void maketree(int rt, int l, int u, int fa) 123 {//构建线段树 124 insert(id_root[rt][l], 0, n, d[u], val[u]); 125 for (int i = Head[u], v; i!=-1; i = edge[i].next) 126 { 127 v = edge[i].to; 128 if (v == fa || vis[v]) continue; 129 d[v] = d[u] + 1; 130 maketree(rt, l, v, u); 131 } 132 } 133 inline void getroot(int u, int fa) 134 {//获取树的重心 135 nums[u] = 1; 136 mxson[u] = 0; 137 for (int i = Head[u], v; i!=-1; i = edge[i].next) 138 { 139 v = edge[i].to; 140 if (v == fa || vis[v]) continue; 141 getroot(v, u); 142 nums[u] += nums[v]; 143 mxson[u] = Max(mxson[u], nums[v]); 144 } 145 mxson[u] = Max(mxson[u], sn - nums[u]); 146 if (mxson[u] < mxson[root]) root = u; 147 } 148 inline void pointDivide(int u) 149 {//构建点分树 150 vis[u] = 1; d[u] = 0; 151 maketree(u, 0, u, 0); 152 for (int i = Head[u], v; i!=-1; i = edge[i].next) 153 { 154 v = edge[i].to; 155 if (vis[v]) continue; 156 sn = nums[v]; root = 0; 157 d[v] = 1; 158 getroot(v, 0); 159 maketree(root, 1, v, u); 160 pre[root] = u; 161 pointDivide(root); 162 } 163 } 164 inline int ask(int u, int k) 165 { 166 int ret = query(id_root[u][0], 0, n, 0, k); 167 for (int i = u; pre[i]; i = pre[i]) 168 { 169 int rem = dist(u, pre[i]); 170 ret += query(id_root[pre[i]][0], 0, n, 0, k - rem); 171 ret -= query(id_root[i][1], 0, n, 0, k - rem); 172 } 173 return ret; 174 } 175 inline void update(int u, int k) 176 { 177 int sub = k - query(id_root[u][0], 0, n, 0, 0); 178 insert(id_root[u][0], 0, n, 0, sub); 179 for (int i = u; pre[i]; i = pre[i]) 180 { 181 int cn = dist(u, pre[i]); 182 insert(id_root[pre[i]][0], 0, n, cn, sub); 183 insert(id_root[i][1], 0, n, cn, sub); 184 } 185 } 186 int main(void) 187 { 188 memset(Head, -1, sizeof(Head)); 189 read(n), read(m); 190 for (int i = 1; i <= n; i++) read(val[i]); 191 for (int i = 1, u, v; i < n; i++) { 192 read(u), read(v); 193 add(u, v); 194 } 195 sn = mxson[0] = n; 196 lca_dfs(1, 0); 197 getroot(1, 0); 198 pointDivide(root); 199 200 for (int i = 1, op, x, y; i <= m; i++) { 201 read(op), read(x), read(y); 202 x ^= ans, y ^= ans; 203 if (op) update(x, y); 204 else write(ans = ask(x, y)); 205 } 206 return 0; 207 }
6、Boatherds POJ - 2114
题意:求树上是否存在点对,其两点之间路径和为恰好为K。
思路:点分治。计算子树所有结点到当前根的距离,排序后二分查找计算点对的数目。(注:在solve函数中调用getdis前如果将dis数组memset一下就会超时,去掉后4、5百ms就能过,很迷。)
1 #include<iostream> 2 #include<cstdio> 3 #include<algorithm> 4 #include<cstring> 5 using namespace std; 6 const int maxn = 10000 + 10; 7 const int INF = 0x3f3f3f3f; 8 struct EDGE 9 { 10 int from, to, w, next; 11 EDGE(int ff = 0, int tt = 0, int ww = 0, int nn = 0) :from(ff), to(tt), w(ww), next(nn) {} 12 }edge[maxn << 1]; 13 int Head[maxn], totedge; 14 void addedge(int from, int to, int w) 15 { 16 edge[totedge] = EDGE(from, to, w, Head[from]); 17 Head[from] = totedge++; 18 edge[totedge] = EDGE(to, from, w, Head[to]); 19 Head[to] = totedge++; 20 } 21 22 int root, MX;//树的重心、找到的最大子树的最小结点数目 23 int nums[maxn];//i为根,子树的结点数量(包括自己) 24 int mxson[maxn];//i为根,最大的子树 25 bool vis[maxn]; 26 int n, ans, K;//输入的树的大小、结果、指定的路径长度 27 int sn;//当前根下,总树的大小 28 29 30 void getRoot(int u, int fa) 31 {//找到树的重心 32 nums[u] = 1, mxson[u] = 0; 33 for (int i = Head[u]; i != -1; i = edge[i].next) 34 { 35 int v = edge[i].to; 36 if (vis[v] || v == fa)continue; 37 getRoot(v, u); 38 nums[u] += nums[v]; 39 mxson[u] = max(mxson[u], nums[v]); 40 } 41 mxson[u] = max(mxson[u], sn - nums[u]);// n - nums[u]是经过父亲节点的子树大小 42 if (mxson[u] < MX) root = u, MX = mxson[u]; 43 44 } 45 int dis[maxn],cnt;//记录当前得到的距离数目 46 void getdis(int u, int fa, int dist) 47 {//找到子树所有结点到当前根的距离 48 if(dist<=K)dis[++cnt] = dist; 49 for (int i = Head[u]; i != -1; i = edge[i].next) 50 { 51 int v = edge[i].to; 52 if (vis[v] || v == fa) continue; 53 getdis(v, u, dist + edge[i].w); 54 } 55 } 56 int solve(int r, int len) 57 { 58 cnt = 0; 59 getdis(r, 0, len); 60 sort(dis + 1, dis + 1 + cnt); 61 int L = 1, R = cnt; 62 int tans = 0; 63 while (L< R) 64 { 65 if (dis[R] + dis[L] == K) 66 { 67 if (dis[L] == dis[R]) 68 { 69 tans += (R - L + 1)*(R - L) / 2; 70 break; 71 } 72 int ll = L, rr = R; 73 while (dis[ll] == dis[L]) ll++; 74 while (dis[rr] == dis[R]) rr--; 75 tans += (ll - L)*(R - rr); 76 L=ll,R=rr; 77 } 78 else if (dis[R] + dis[L] < K) L++; 79 else R--; 80 } 81 return tans; 82 83 } 84 void pointDivide(int tr) 85 { 86 ans += solve(tr, 0);//求解经过tr的所有路径(点对) 87 vis[tr] = true;//当前点标记 88 for (int i = Head[tr]; i != -1; i = edge[i].next) 89 {//分治处理每一棵子树 90 int v = edge[i].to; 91 if (vis[v]) continue; 92 ans -= solve(v, edge[i].w);//容斥去除重复部分 93 sn = nums[v];//重设当前总树大小,寻找新的分治点(重心) 94 root = 0; 95 MX = INF; 96 getRoot(v, 0); 97 pointDivide(root); //递归处理新的分治点 98 } 99 } 100 101 int main() 102 { 103 while (~scanf("%d", &n)&&n) 104 { 105 memset(Head, -1, sizeof(Head)); 106 totedge = 0; 107 for (int i = 1; i <= n; i++) 108 { 109 int v, w; 110 while (~scanf("%d", &v) && v) 111 { 112 scanf("%d", &w); 113 addedge(i, v, w); 114 } 115 } 116 while (~scanf("%d", &K) && K) 117 { 118 memset(vis, 0, sizeof(vis)); 119 MX = INF; 120 ans = 0; 121 sn = n; 122 getRoot(1, 0); 123 pointDivide(root); 124 if (ans) printf("AYE "); 125 else printf("NAY "); 126 } 127 printf(". "); 128 } 129 return 0; 130 }
7、Distance Statistics POJ - 1987
题意:求树上距离小于等于k的点对数目。
思路:点分治,同poj 1741。
1 #include<iostream> 2 #include<cstdio> 3 #include<algorithm> 4 using namespace std; 5 const int maxn = 40000 + 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,m;//输入的树的大小、结果、指定的路径长度的最大值 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 char dir[20]; 100 int main() 101 { 102 while (~scanf("%d%d", &n, &m)) 103 { 104 init(); 105 for (int i = 1; i <= m; i++) 106 { 107 int u, v, w; 108 scanf("%d%d%d%s", &u, &v, &w,dir); 109 addedge(u, v, w); 110 } 111 scanf("%d", &k); 112 getRoot(1, 0); 113 pointDivide(root); 114 printf("%d ", ans); 115 } 116 return 0; 117 }
8、Balancing Act POJ - 1655
题意:求树上一点,其最大子树的节点数最小。
思路:求解树的重心。
1 #include<iostream> 2 #include<cstdio> 3 #include<algorithm> 4 using namespace std; 5 const int maxn = 20000 + 10; 6 const int INF = 0x3f3f3f3f; 7 struct EDGE 8 { 9 int from, to, next; 10 EDGE(int ff = 0, int tt = 0, int nn = 0) :from(ff), to(tt), next(nn) {} 11 }edge[maxn << 1]; 12 int Head[maxn], totedge; 13 void addedge(int from, int to) 14 { 15 edge[totedge] = EDGE(from, to, Head[from]); 16 Head[from] = totedge++; 17 edge[totedge] = EDGE(to, from, 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;//输入的树的大小、结果 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 53 int main() 54 { 55 int t; 56 scanf("%d", &t); 57 58 while (t--) 59 { 60 scanf("%d", &n); 61 init(); 62 for (int i = 1; i <= n - 1; i++) 63 { 64 int u, v; 65 scanf("%d%d", &u, &v); 66 addedge(u, v); 67 } 68 getRoot(1, 0); 69 printf("%d %d ", root,MX); 70 } 71 return 0; 72 }
9、最短路径树问题 HYSBZ - 4016//////Shortest-path tree hdu-4871
题意:给出一个无向图,求最短路径树(有多个最短路时,选择路径结点的字典序较小的),在树上求路径含有结点数为k的最长路径以及对应的路径数目。
思路:最短路+点分治。
1、求最短路径树:先对边排序,因为是最后用链式前向星建树,所以排序时对入点从大到小排,这样加入链式前向星是从小到大的,spfa后dfs建树。
2、点分治。用dis[maxn], Count[maxn]维护路径上结点数为i时(不计根)的最大路径长度以及条数。对于当前重心u,处理其每棵子树,先更新答案值,再更新dis、Count数组。
3、hdu是多组输入,注意调整下代码即可。
4、每次对树的重心操作时,需要重新对dis、Count数组初始化,其上限为当前树的最大深度。
1 #include<iostream> 2 #include<cstdio> 3 #include<queue> 4 #include<algorithm> 5 #include<cstring> 6 using namespace std; 7 const int maxn = 30000 + 10; 8 const int maxm = 60000 + 10; 9 const int INF = 0x3f3f3f3f; 10 struct EDGE 11 { 12 int from, to, w, next; 13 EDGE(int ff = 0, int tt = 0, int ww = 0, int nn = 0) :from(ff), to(tt), w(ww), next(nn) {} 14 friend bool operator<(const EDGE&e1, const EDGE&e2) 15 { 16 if (e1.from == e2.from) return e1.to > e2.to; 17 else return e1.from < e2.from; 18 } 19 }edge1[maxn << 1], edge2[maxm << 1], edge3[maxm << 1]; 20 int Head1[maxn], Head2[maxn], totedge1, totedge2; 21 void addedge(EDGE *edge, int *Head, int &totedge, int from, int to, int w) 22 { 23 edge[totedge] = EDGE(from, to, w, Head[from]); 24 Head[from] = totedge++; 25 edge[totedge] = EDGE(to, from, w, Head[to]); 26 Head[to] = totedge++; 27 } 28 29 int root, MX;//树的重心、找到的最大子树的最小结点数目 30 int nums[maxn];//i为根,子树的结点数量(包括自己) 31 int mxson[maxn];//i为根,最大的子树 32 bool vis[maxn]; 33 int n;//输入的树的大小、结果 34 int m, k; 35 int sn;//当前根下,总树的大小 36 int ans1, ans2; 37 int mxdep; 38 void init() 39 { 40 memset(Head1, -1, sizeof(Head1)); 41 memset(Head2, -1, sizeof(Head2)); 42 totedge1 = totedge2 = 0; 43 memset(vis, 0, sizeof(vis)); 44 MX = INF; 45 ans1 = 0, ans2 = 0; 46 sn = n; 47 } 48 void getRoot(EDGE *edge, int *Head, int u, int fa) 49 {//找到树的重心 50 nums[u] = 1, mxson[u] = 0; 51 for (int i = Head[u]; i != -1; i = edge[i].next) 52 { 53 int v = edge[i].to; 54 if (vis[v] || v == fa)continue; 55 getRoot(edge, Head, v, u); 56 nums[u] += nums[v]; 57 mxson[u] = max(mxson[u], nums[v]); 58 } 59 mxson[u] = max(mxson[u], sn - nums[u]);// n - nums[u]是经过父亲节点的子树大小 60 if (mxson[u] < MX) root = u, MX = mxson[u]; 61 62 } 63 int dis[maxn], Count[maxn];//路径上结点数为i时(不计根)的最大路径长度以及条数 64 void getdis(EDGE *edge, int *Head, int u, int fa, int dist, int dep) 65 { 66 if (dep >= k) return; 67 if (dist > dis[dep]) dis[dep] = dist, Count[dep] = 1; 68 else if (dist == dis[dep]) Count[dep]++; 69 for (int i = Head[u]; i != -1; i = edge[i].next) 70 { 71 int v = edge[i].to; 72 if (vis[v] || v == fa) continue; 73 getdis(edge, Head, v, u, dist + edge[i].w, dep + 1); 74 } 75 } 76 void updatedis(EDGE *edge, int *Head, int u, int fa, int dist, int dep) 77 { 78 if (dep >= k) return; 79 if (Count[k - dep - 1] && dist + dis[k - dep - 1] > ans1) 80 { 81 ans1 = dist + dis[k - dep - 1], ans2 = Count[k - dep - 1]; 82 } 83 else if (dist + dis[k - dep - 1] == ans1) ans2 += Count[k - dep - 1]; 84 for (int i = Head[u]; i != -1; i = edge[i].next) 85 { 86 int v = edge[i].to; 87 if (vis[v] || v == fa) continue; 88 updatedis(edge, Head, v, u, dist + edge[i].w, dep + 1); 89 } 90 } 91 void get_mxdep(EDGE *edge, int *Head, int u, int fa, int dep) 92 { 93 mxdep = max(mxdep, dep); 94 for (int i = Head[u]; i != -1; i = edge[i].next) 95 { 96 int v = edge[i].to; 97 if (vis[v] || v == fa) continue; 98 get_mxdep(edge, Head, v, u, dep + 1); 99 } 100 } 101 void solve(EDGE *edge, int *Head, int r) 102 { 103 mxdep = 0; 104 get_mxdep(edge, Head, r, 0, 0); 105 for (int i = 1; i <=k; i++) dis[i] = Count[i] = 0; 106 107 dis[0] = 0, Count[0] = 1; 108 for (int i = Head[r]; i != -1; i = edge[i].next) 109 { 110 int v = edge[i].to; 111 if (vis[v]) continue; 112 updatedis(edge, Head, v, r, edge[i].w, 1); 113 getdis(edge, Head, v, r, edge[i].w, 1); 114 } 115 } 116 void pointDivide(EDGE *edge, int *Head, int tr) 117 { 118 vis[tr] = true;//当前点标记 119 solve(edge, Head, tr); 120 for (int i = Head[tr]; i != -1; i = edge[i].next) 121 { 122 int v = edge[i].to; 123 if (vis[v]) continue; 124 sn = nums[v];//重设当前总树大小,寻找新的分治点(重心) 125 root = 0; 126 MX = INF; 127 getRoot(edge, Head, v, 0); 128 pointDivide(edge, Head, root); //递归处理新的分治点 129 } 130 } 131 int dis_spfa[maxn]; 132 bool spfa(int rt) 133 { 134 for (int i = 1; i <= n; i++) dis_spfa[i] = INF; 135 dis_spfa[rt] = 0, vis[rt] = true; 136 queue<int>q; 137 q.push(rt); 138 while (!q.empty()) 139 { 140 int u = q.front(); 141 q.pop(); 142 vis[u] = false; 143 for (int i = Head2[u]; i != -1; i = edge2[i].next) 144 { 145 int v = edge2[i].to, w = edge2[i].w; 146 if (dis_spfa[v] > dis_spfa[u] + w) 147 { 148 dis_spfa[v] = dis_spfa[u] + w; 149 if (!vis[v]) 150 { 151 vis[v] = true; 152 q.push(v); 153 } 154 } 155 } 156 } 157 } 158 void maketree(int rt, int fa) 159 { 160 vis[rt] = true; 161 for (int i = Head2[rt]; i != -1; i = edge2[i].next) 162 { 163 int to = edge2[i].to, w = edge2[i].w; 164 if (!vis[to] && dis_spfa[to] == dis_spfa[rt] + w && to != fa) 165 { 166 addedge(edge1, Head1, totedge1, rt, to, w); 167 maketree(to, rt); 168 } 169 } 170 } 171 int main() 172 { 173 int t; 174 scanf("%d", &t); 175 while (t--) 176 { 177 scanf("%d%d%d", &n, &m, &k); 178 init(); 179 int cnt = 0; 180 for (int i = 1; i <= m; i++) 181 { 182 int u, v, w; 183 scanf("%d%d%d", &u, &v, &w); 184 edge3[cnt++] = EDGE(u, v, w); 185 edge3[cnt++] = EDGE(v, u, w); 186 } 187 sort(edge3, edge3 + cnt); 188 for (int i = 0; i < cnt; i++) 189 { 190 int from = edge3[i].from, to = edge3[i].to, w = edge3[i].w; 191 edge2[totedge2] = EDGE(from, to, w, Head2[from]); 192 Head2[from] = totedge2++; 193 } 194 spfa(1); 195 memset(vis, 0, sizeof(vis)); 196 maketree(1, 0); 197 memset(vis, 0, sizeof(vis)); 198 getRoot(edge1, Head1, 1, 0); 199 pointDivide(edge1, Head1, root); 200 printf("%d %d ", ans1, ans2); 201 } 202 return 0; 203 } 204 /* 205 6 6 3 206 1 2 2 207 1 3 1 208 2 4 7 209 2 6 3 210 3 5 1 211 5 6 3 212 213 10 1 214 */