不能说是一个算法,应该算是一类思想
点分治
概念
点分治就是把树上问题中的节点拿来分治。
这所谓的“分治”是一个很抽象的概念,那么就先来介绍它的常见应用和其他性质。
大致框架
1 void getRoot(int x, int fa) //找重心
2 {
3 size[x] = 1, son[x] = 0;
4 for (int i=head[x]; i!=-1; i=nxt[i])
5 {
6 int v = edges[i].y;
7 if (v==fa||vis[v]) continue; //在点分树内寻找重心
8 getRoot(v, x), size[x] += size[v];
9 son[x] = std::max(son[x], size[v]);
10 }
11 son[x] = std::max(son[x], tot-size[x]);
12 if (son[x] < son[root]) root = x; //root即重心
13 }
14 void dfs(int x, int fa, int c)
15 {
16 record distance_c //将长度为c的路径记录下来
17 for (int i=head[x]; i!=-1; i=nxt[i])
18 {
19 int v = edges[i].y;
20 if (v==fa||vis[v]) continue;
21 dfs(v, x, c+edges[i].val); //dfs下去的长度加上边长
22 }
23 }
24 int calc(int x, int c) //为了容斥而存在的calculate
25 {
26 int ret = 0;
27 dfs(x, 0, c);
28 ...
29 return ret;
30 }
31 void deal(int rt)
32 {
33 ans += calc(rt, 0), vis[rt] = 1; //vis[rt]表示点rt被作为重心分治过
34 for (int i=head[rt]; i!=-1; i=nxt[i])
35 {
36 int v = edges[i].y;
37 if (vis[v]) continue;
38 ans -= calc(v, edges[i].val); //容斥
39 root = 0, tot = size[v];
40 getRoot(v, 0), deal(v);
41 }
42 }
43 int main()
44 {
45 ...
46 getRoot(1, 0);
47 deal(root);
48 ...
49 return 0;
50 }
常见应用
统计树上点对路径长度为$d=k$的条数
显然路径规模是$O(n^2)$的。
注意到这$n^2$路径间有很多共用的部分。

对于有重叠的路径,可以看做这样的至少有一个重叠点的形式。
自然想到类似的“按边统计贡献”的方式,对于点来统计路径的长度。显然这样可以重复利用大量的共同信息。
1 void deal(int rt)
2 {
3 vis[rt] = num[0] = 1, sv[0] = 0;
4 for (int i=head[rt]; i!=-1; i=nxt[i])
5 {
6 int v = edges[i].y;
7 if (vis[v]) continue;
8 top = 0, dis[v] = edges[i].val, dfs(v, rt);
9 for (int i=1; i<=top; i++) query(stk[i]); //当前有一条长度为stk[i]的链,并在之前储存过的链长中匹配
10 for (int i=1; i<=top; i++) num[stk[i]] = 1, sv[++sv[0]] = stk[i]; //标记链长为stk[i]的链
11 }12 }
大概是这样的代码。
注意到若选取了一个点来计算经过路径数,为了计算不重复,相当于操作后在树上就要将这个点和与之相连的边都删去。
于是选取子树的重心使得复杂度在树退化为链的最坏情况下降低为$O(nlogn)$。
统计树上点对路径长度为$d≡k(mod p)$的条数
由于一般询问的都是简单路径,那么需要稍稍容斥一下。将过重心的路径划分剩余类,那么记长度为$x$的路径的个数为$cnt[x]$。例如当$p=3$时,答案就是每一次deal时$ans+=cnt[1]*cnt[2]*2+cnt[0]*cnt[0]$,再在重心子树时$ans-=cnt[1]*cnt[2]*2+cnt[0]*cnt[0]$。
其他性质
纯粹点分治的时间复杂度是$O(nlogn)$的。
考虑树退化为链的最坏情况:每一次分治,在长度为$d$的链上,当前的重心将链分为了两条长度不超过$d/2$的链。考虑递归过程的终止情况即链长为1。那么总复杂度就为$1*(n/1)+2*(n/2)+4*(n/4)+...+n*(n/n)=nlogn$。
点分治题目
【$q=k$路径】P3806 【模板】点分治1
题目背景
感谢hzwer的点分治互测。
题目描述
给定一棵有n个点的树
询问树上距离为k的点对是否存在。
输入输出格式
输入格式:
n,m 接下来n-1条边a,b,c描述a到b有一条长度为c的路径
接下来m行每行询问一个K
输出格式:
对于每个K每行输出一个答案,存在输出“AYE”,否则输出”NAY”(不包含引号)
说明
对于30%的数据n<=100
对于60%的数据n<=1000,m<=50
对于100%的数据n<=10000,m<=100,c<=1000,K<=10000000
题目分析
题目只要求判断路径是否存在。
那么先读进询问,再点分治一下,询问路径长度就可以了。
1 #include<bits/stdc++.h>
2 const int maxn = 10035;
3 const int maxk = 10000035;
4
5 struct Edge
6 {
7 int y,val;
8 Edge(int a=0, int b=0):y(a),val(b) {}
9 }edges[maxn<<1];
10 int stk[maxn],top;
11 int n,m,tot,root,q[maxn],sv[maxn];
12 int edgeTot,nxt[maxn<<1],head[maxn];
13 int size[maxn],dis[maxn],son[maxn];
14 bool vis[maxn],ans[maxn],num[maxk];
15
16 int read()
17 {
18 char ch = getchar();
19 int num = 0;
20 bool fl = 0;
21 for (; !isdigit(ch); ch = getchar())
22 if (ch=='-') fl = 1;
23 for (; isdigit(ch); ch = getchar())
24 num = (num<<1)+(num<<3)+ch-48;
25 if (fl) num = -num;
26 return num;
27 }
28 void getRoot(int x, int fa)
29 {
30 size[x] = 1, son[x] = 0;
31 for (int i=head[x]; i!=-1; i=nxt[i])
32 {
33 int v = edges[i].y;
34 if (v==fa||vis[v]) continue;
35 getRoot(v, x), size[x] += size[v];
36 son[x] = std::max(son[x], size[v]);
37 }
38 son[x] = std::max(son[x], n-size[x]);
39 if (son[x] < son[root]) root = x;
40 }
41 void addedge(int u, int v, int c)
42 {
43 edges[++edgeTot] = Edge(v, c), nxt[edgeTot] = head[u], head[u] = edgeTot;
44 edges[++edgeTot] = Edge(u, c), nxt[edgeTot] = head[v], head[v] = edgeTot;
45 }
46 void query(int x)
47 {
48 for (int i=1; i<=m; i++)
49 if (q[i] >= x) ans[i] |= num[q[i]-x];
50 }
51 void dfs(int x, int fa)
52 {
53 stk[++top] = dis[x];
54 for (int i=head[x]; i!=-1; i=nxt[i])
55 {
56 int v = edges[i].y;
57 if (v==fa||vis[v]) continue;
58 dis[v] = dis[x]+edges[i].val, dfs(v, x);
59 }
60 }
61 void deal(int rt)
62 {
63 vis[rt] = num[0] = 1, sv[0] = 0;
64 for (int i=head[rt]; i!=-1; i=nxt[i])
65 {
66 int v = edges[i].y;
67 if (vis[v]) continue;
68 top = 0, dis[v] = edges[i].val, dfs(v, rt);
69 for (int i=1; i<=top; i++) query(stk[i]);
70 for (int i=1; i<=top; i++) num[stk[i]] = 1, sv[++sv[0]] = stk[i];
71 }
72 for (int i=1; i<=sv[0]; i++) num[sv[i]] = 0;
73 for (int i=head[rt]; i!=-1; i=nxt[i])
74 {
75 int v = edges[i].y;
76 if (vis[v]) continue;
77 root = 0, tot = size[v];
78 getRoot(v, 0), deal(root);
79 }
80 }
81 int main()
82 {
83 memset(head, -1, sizeof head);
84 son[0] = tot = n = read(), m = read(), root = 0;
85 for (int i=1; i<n; i++)
86 {
87 int u = read(), v = read();
88 addedge(u, v, read());
89 }
90 for (int i=1; i<=m; i++) q[i] = read();
91 getRoot(1, 0);
92 deal(root);
93 for (int i=1; i<=m; i++)
94 if (ans[i])
95 puts("AYE");
96 else puts("NAY");
97 return 0;
98 }
【$q≡k$路径】bzoj2152: 聪聪可可
Description
聪聪和可可是兄弟俩,他们俩经常为了一些琐事打起来,例如家中只剩下最后一根冰棍而两人都想吃、两个人都想玩儿电脑(可是他们家只有一台电脑)……遇到这种问题,一般情况下石头剪刀布就好了,可是他们已经玩儿腻了这种低智商的游戏。他们的爸爸快被他们的争吵烦死了,所以他发明了一个新游戏:由爸爸在纸上画n个“点”,并用n-1条“边”把这n个“点”恰好连通(其实这就是一棵树)。并且每条“边”上都有一个数。接下来由聪聪和可可分别随即选一个点(当然他们选点时是看不到这棵树的),如果两个点之间所有边上数的和加起来恰好是3的倍数,则判聪聪赢,否则可可赢。聪聪非常爱思考问题,在每次游戏后都会仔细研究这棵树,希望知道对于这张图自己的获胜概率是多少。现请你帮忙求出这个值以验证聪聪的答案是否正确。
Input
输入的第1行包含1个正整数n。后面n-1行,每行3个整数x、y、w,表示x号点和y号点之间有一条边,上面的数是w。
Output
以即约分数形式输出这个概率(即“a/b”的形式,其中a和b必须互质。如果概率为1,输出“1/1”)。
Sample Input
1 2 1
1 3 2
1 4 1
2 5 3
Sample Output
【样例说明】
13组点对分别是(1,1) (2,2) (2,3) (2,5) (3,2) (3,3) (3,4) (3,5) (4,3) (4,4) (5,2) (5,3) (5,5)。
【数据规模】
对于100%的数据,n<=20000。
题目分析
乍一看好像要像上一种方法一样,按照p,2p,3p枚举下去。
然则可以灵活地控制点分治中deal操作的内容。
将以重心为起点的链长度划分剩余类,即可得到$cnt[0],cnt[1],cnt[2]$。由于题目要求的是有序点对,自然答案应该是包括了$cnt[0]*cnt[0]+2*cnt[1]*cnt[2]$。
之所以用了“包括”一次,是因为若两条链相重叠,统计时就会重复。那么只需要容斥地去将重心每一个子树内的贡献减去就行了,恰好这个过程可以放在deal操作内。
1 #include<bits/stdc++.h>
2 const int maxn = 20035;
3
4 struct Edge
5 {
6 int y,val;
7 Edge(int a=0, int b=0):y(a),val(b) {}
8 }edges[maxn<<1];
9 int n,ans;
10 int edgeTot,nxt[maxn<<1],head[maxn];
11 int tot,root,son[maxn],size[maxn];
12 int cnt[4],dis[maxn];
13 bool vis[maxn];
14
15 int read()
16 {
17 char ch = getchar();
18 int num = 0;
19 bool fl = 0;
20 for (; !isdigit(ch); ch = getchar())
21 if (ch=='-') fl = 1;
22 for (; isdigit(ch); ch = getchar())
23 num = (num<<1)+(num<<3)+ch-48;
24 if (fl) num = -num;
25 return num;
26 }
27 int gcd(int a, int b){return b==0?a:gcd(b, a%b);}
28 void addedge(int u, int v, int c)
29 {
30 edges[++edgeTot] = Edge(v, c), nxt[edgeTot] = head[u], head[u] = edgeTot;
31 edges[++edgeTot] = Edge(u, c), nxt[edgeTot] = head[v], head[v] = edgeTot;
32 }
33 void getRoot(int x, int fa)
34 {
35 size[x] = 1, son[x] = 0;
36 for (int i=head[x]; i!=-1; i=nxt[i])
37 {
38 int v = edges[i].y;
39 if (v==fa||vis[v]) continue;
40 getRoot(v, x), size[x] += size[v];
41 son[x] = std::max(son[x], size[v]);
42 }
43 son[x] = std::max(son[x], tot-size[x]);
44 if (son[x] < son[root]) root = x;
45 }
46 void dfs(int x, int fa)
47 {
48 cnt[dis[x]]++;
49 for (int i=head[x]; i!=-1; i=nxt[i])
50 {
51 int v = edges[i].y;
52 if (v==fa||vis[v]) continue;
53 dis[v] = (dis[x]+edges[i].val)%3;
54 dfs(v, x);
55 }
56 }
57 int calc(int x, int c)
58 {
59 dis[x] = c, cnt[0] = cnt[1] = cnt[2] = 0;
60 dfs(x, 0);
61 return 2*cnt[1]*cnt[2]+cnt[0]*cnt[0];
62 }
63 void deal(int rt)
64 {
65 ans += calc(rt, 0), vis[rt] = 1;
66 for (int i=head[rt]; i!=-1; i=nxt[i])
67 {
68 int v = edges[i].y;
69 if (vis[v]) continue;
70 ans -= calc(v, edges[i].val);
71 }
72 for (int i=head[rt]; i!=-1; i=nxt[i])
73 {
74 int v = edges[i].y;
75 if (vis[v]) continue;
76 root = 0, tot = size[v];
77 getRoot(v, 0), deal(root);
78 }
79 }
80 int main()
81 {
82 memset(head, -1, sizeof head);
83 tot = son[0] = n = read(), ans = root = 0;
84 for (int i=1; i<n; i++)
85 {
86 int u = read(), v = read();
87 addedge(u, v, read()%3);
88 }
89 getRoot(1, 0);
90 deal(root);
91 int gc = gcd(ans, n*n);
92 printf("%d/%d\n",ans/gc,n*n/gc);
93 return 0;
94 }
【$q≥k$路径】bzoj1468: Tree
Description
Input
Output
Sample Input
1 6 13
6 3 9
3 5 7
4 1 3
2 4 20
4 7 2
10
Sample Output
题目分析
其实和$q=k$的做法相差不大,只需要将过重心的路径存下来后线性扫一遍就好了。
1 #include<bits/stdc++.h>
2 const int maxn = 40035;
3
4 struct Edge
5 {
6 int y,val;
7 Edge(int a=0, int b=0):y(a),val(b) {}
8 }edges[maxn<<1];
9 int edgeTot,nxt[maxn<<1],head[maxn];
10 int dis[maxn],son[maxn],size[maxn],root,tot;
11 int n,k,ans,sv[maxn];
12 bool vis[maxn];
13
14 int read()
15 {
16 char ch = getchar();
17 int num = 0;
18 bool fl = 0;
19 for (; !isdigit(ch); ch = getchar())
20 if (ch=='-') fl = 1;
21 for (; isdigit(ch); ch = getchar())
22 num = (num<<1)+(num<<3)+ch-48;
23 if (fl) num = -num;
24 return num;
25 }
26 void addedge(int u, int v, int c)
27 {
28 edges[++edgeTot] = Edge(v, c), nxt[edgeTot] = head[u], head[u] = edgeTot;
29 edges[++edgeTot] = Edge(u, c), nxt[edgeTot] = head[v], head[v] = edgeTot;
30 }
31 void getRoot(int x, int fa)
32 {
33 size[x] = 1, son[x] = 0;
34 for (int i=head[x]; i!=-1; i=nxt[i])
35 {
36 int v = edges[i].y;
37 if (v==fa||vis[v]) continue;
38 getRoot(v, x), size[x] += size[v];
39 son[x] = std::max(son[x], size[v]);
40 }
41 son[x] = std::max(son[x], tot-size[x]);
42 if (son[x] < son[root]) root = x;
43 }
44 void dfs(int x, int fa, int c)
45 {
46 sv[++sv[0]] = c;
47 for (int i=head[x]; i!=-1; i=nxt[i])
48 {
49 int v = edges[i].y;
50 if (v==fa||vis[v]) continue;
51 dfs(v, x, c+edges[i].val);
52 }
53 }
54 int calc(int x, int c)
55 {
56 int ret = 0,l,r;
57 sv[0] = 0, dfs(x, 0, c);
58 std::sort(sv+1, sv+sv[0]+1);
59 l = 1, r = sv[0];
60 while (l <= r)
61 if (sv[l]+sv[r] <= k) ret += r-l, l++;
62 else r--;
63 return ret;
64 }
65 void deal(int rt)
66 {
67 ans += calc(rt, 0), vis[rt] = 1;
68 for (int i=head[rt]; i!=-1; i=nxt[i])
69 {
70 int v = edges[i].y;
71 if (vis[v]) continue;
72 ans -= calc(v, edges[i].val);
73 root = 0, tot = size[v];
74 getRoot(v, 0), deal(v);
75 }
76 }
77 int main()
78 {
79 memset(head, -1, sizeof head);
80 tot = son[0] = n = read(), root = 0;
81 for (int i=1; i<n; i++)
82 {
83 int u = read(), v = read();
84 addedge(u, v, read());
85 }
86 k = read();
87 getRoot(1, 0);
88 deal(root);
89 printf("%d\n",ans);
90 return 0;
91 }
END
来源:https://www.cnblogs.com/antiquality/p/9429513.html