点分治
点分治的适用范围
点分治(starch)用于 \(O(n\log n)\) 统计树上特定结构(例如路径、连通块等)的信息(大小、数量、最值、颜色数等)。“特定结构”需要满足以下条件:
- 由一些点组成;
- 可以由其包含的任意一个点统计贡献(对于其内部的每个点,统计出的贡献都是相同的);
- 任选一个节点作为根,这类结构中的任意一个都满足:要么包含根节点,要么完全包含于根节点的一个子树内。
一般,使用点分治统计路径和连通块是较为常见的。
点分治的算法流程如下:
- 收到一个连通块,需要统计完全包含于当前连通块的贡献;
get_sz
get_rt
:两遍dfs
找到连通块的重心 \(rt\);- 将 \(rt\) 标记为“禁止经过”;
calc
:统计经过 \(rt\) 的路径(或连通块)的贡献;- 枚举 \(rt\) 的出边 \(rt\to v\),递归
solve(v)
;由于 \(rt\) 被标记为禁止经过,因此子树 \(v\) 不会跨过 \(u\) 去处理子树外面的部分;
注意到路径(或连通块)要么跨过 \(rt\) 节点,被本轮 calc
统计,要么完全包含于 \(rt\) 的一个子树,被子树递归统计。
记 \(size[u]\) 表示 solve(u)
时 \(u\) 所在的连通块的大小。容易证明:\(\sum size[u]\) 是 \(O(n\log n)\) 级别的。
因为每分治一层,每个节点所在的连通块大小就至少减少一半,因此最多分治 \(\log n\) 层,每个节点最多被统计 \(\log n\) 次。因此 \(\sum size[u]\) 是 \(O(n\log n)\) 级别。
得益于这个结论,calc
的时间复杂度可以为 \(O(n)\) 甚至 \(O(n\log n)\)(\(n\) 指当前连通块大小)。也就是说,我们可以在 calc
中遍历整个连通块,算出 \(rt\) 的贡献。
分治过程
在代码中,我们一般将求重心的步骤放到父节点处理。这里传入的 \(u\) 就是重心。
两遍 dfs
寻找重心
寻找重心的依据是重心的定义:重心是最大子树最小的一个节点。第一遍 dfs 求出所有子树大小,第二遍 dfs 求出每个节点的最大子树(包括祖先方向的“子树”),取最小值即是重心。
int rt;
int sz[N], mxs[N] = {INF};
// 获取子树 sz
void get_sz(int u, int f) {
sz[u] = 1;
for(int i = head[u]; i; i = pool[i].next) {
int v = pool[i].v;
if(v == f || vis[v]) continue; // 注意避开已访问的节点
get_sz(v, u);
sz[u] += sz[v];
}
}
// 找到连通块重心
// tot 表示当前连通块的大小
void get_root(int u, int f, int tot) {
mxs[u] = 0;
for(int i = head[u]; i; i = pool[i].next) {
int v = pool[i].v;
if(v == f || vis[v]) continue;
get_root(v, u, tot);
// 统计子树最大值
mxs[u] = max(mxs[u], sz[v]);
}
// 计算祖先方向的“子树”大小
mxs[u] = max(mxs[u], tot - sz[u]);
if(mxs[u] < mxs[rt]) rt = u;
}
P3806 【模板】点分治 1
题意
给定一棵有 \(n\) 个点的树,询问树上距离为 \(k\) 的点对是否存在。
\(n\le 10^4\),时限 200ms
,\(n^2\) 过不去。
即统计长为 \(k\) 的路径数量。
考虑如何在 calc()
中处理经过 \(rt\) 节点的路径。我们只需要逐一考虑每棵子树,用桶数组 f
记录之前子树的信息,然后在遍历结束后把当前子树的信息和 f
合并考虑,算出贡献;最后将当前子树的信息也加入 f
数组。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 |
|
P4149 [IOI 2011] Race
题目大意
给一棵树,每条边有权。求一条简单路径,权值和等于 \(k\),且边的数量最小。
路径长度最值满足点分治的使用条件。考虑 calc(u)
应该如何统计经过节点 \(u\) 的路径。我们沿用模板题的第一种思路,使用 get_dis(v)
函数合并当前子树 \(v\) 和之前的所有子树。使用一个桶数组 f
记录和 \(u\) 距离为 \(i\) 的所有节点距离 \(u\) 的最少边数。记 \(sum\) 表示 \(u\) 到当前节点 \(x\) 的路径边权和,\(len\) 表示 \(u\) 走到当前节点经过的边的数量,统计子树时时使用 f[k - sum] + len
更新答案即可。
注意边权之和 \(sum\) 是没有限制的。因此我们需要在 get_dis()
中剪掉 \(sum>k\) 的节点,否则无法存储在桶数组里。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 |
|
P5311 [Ynoi2011] 成都七中
题目大意
给你一棵 \(n\) 个节点的树,每个节点有一种颜色,有 \(m\) 次查询操作。
查询操作给定参数 \(L,R,x\),需输出:
将树中编号在 \([L,R]\) 内的所有节点保留,\(x\) 所在连通块中颜色种类数。
每次查询操作独立。
我们发现一个点 \(u\) 能对 \(x\) 处的查询产生贡献,当且仅当 \(u\rightarrow x\) 的路径上节点编号的最小值 \(mn\) 满足 \(L\le mn\),最大值 \(mx\) 满足 \(mx\le R\)。因此我们可以从连通块的任意一个节点出发,统计连通块的大小。
我们简化问题,先考虑 \(x\) 固定的情况:以 \(x\) 为根,一遍 dfs
求出 \(x\) 到所有节点的路径的 \(mx\) 和 \(mn\)。然后直接二维数颜色即可。
接下来考虑查询位置 \(x\) 不固定的情况。如果使用换根则无法维护路径最值。这里我们注意到,如果一个询问 \(x\) 和当前的根节点 \(rt\) “连通”(只保留询问的 \([L,R]\) 的节点情况下,两点处于同一连通块),则该询问等价于在当前的根节点上询问。因此我们遍历一遍整棵树,筛选出和根节点连通的询问,然后对它们进行处理。
否则这个询问所在的子树就和根节点以及其他子树分隔开了,成为一个较小的子问题,可以递归处理。考虑使用点分治,每次将根节点 \(rt\) 选为连通块的重心。总时间复杂度为 \(O(n\log n+q\log^2 n)\)。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 |
|
P3714 [BJOI2017] 树的难题
题意
给定一棵 \(n\) 个点的无根树,边有颜色,颜色有 \(m\) 种,第 \(i\) 种颜色的权值为 \(c_i\)。
对于一条树上的简单路径,路径上经过的所有边按顺序组成一个颜色序列,序列可以划分成若干个相同颜色段。定义路径权值为颜色序列上每个同颜色段的颜色权值之和。
请你计算,经过边数在 \(l\) 到 \(r\) 之间的所有简单路径中,路径权值的最大值。
\(n,m\le 2\times 10^5,\ |c_i|\le 10^4\)
将每个节点的出边按照颜色排好序,相同颜色和不同颜色的贡献分开统计,用树状数组维护路径长度即可。
时间复杂度 \(O(n\log^2 n)\)。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 |
|
P4886 快递员
其实不完全是点分治,只是利用重心的 Trick 来减小复杂度。
题意
给你一个包含 \(n\) 个节点的无根树,有 \(m\) 个点对 \((x_i,y_i)\),请你找出一个节点 \(s\),满足 \(\max\{dis(s,x_i)+dis(s,y_i)\}\) 最小。输出最小值。
\(n,m\le 10^5\)
考虑二分答案,发现没法 check
,因此做不了。
尝试挖掘性质,考虑一个调整的过程,设当前节点为 \(s\),考虑 \(d(x_i)+d(y_i)\) 取到最大值那些的 \((x_i,y_i)\),
- 若 \(s\) 位于 \(x_i\to y_i\) 的路径上,那么无论如何答案也不可能更优了;
- 若这些 \((x_i,y_i)\) 都位于 \(s\) 的同一棵子树内,那么将 \(s\) 向这棵子树内调整,才可能更优;
- 若这些 \((x_i,y_i)\) 不都位于同一棵子树内,那么无论如何答案也不可能更优了。
然而,如果我们暴力调整,时间复杂度将达到 \(O(n^2)\)。但我们注意到,可能成为最优解的点的集合总是形如一个连通块,每次出现第二种情况,相当于把 \(s\) 的一堆子树都排除了,范围缩小到了一个更小的子树。这启发我们将 \(s\) 选在重心。发现这样做只需要 \(\log\) 次就能将范围缩小到一个点。
时间复杂度 \(O(m\log n)\)。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 |
|
P7215 [JOISC 2020] 首都
题意
给定一棵树,点有颜色 \(col\)。你可以进行若干次操作,每次操作你可以将一个颜色的所有点的颜色都替换为另一种颜色。问最少替换多少次能产生一种颜色,使得这个颜色的所有节点连通。
\(n\le 2\times 10^5\)
我们有一种暴力的方法:枚举每个颜色为最终颜色,将所有影响它连通性的颜色都吞并,最终就是答案。考虑答案的连通块,它由若干个颜色对应的节点组合起来,不难发现由其中任何一种颜色开始,得到的答案都是相同的。这启发我们使用点分治。
考虑点分治,假设我们当前拿到了一个连通块,分治中心为 \(u\),我们需要求出以 \(col[u]\) 开始,达到连通的代价。首先注意到,如果 \(col[u]\) 存在位于连通块之外的部分,那么达到连通一定需要经过父亲,而父亲的贡献不应被考虑,因此遇到这种情况直接舍去。
否则,我们枚举 \(col[u]\) 出现的所有位置(肯定在连通块内),将它们到 \(u\) 路径上的所有颜色也都加入考虑集合,像 \(u\) 一样处理;如果考虑集合内的某个颜色也在连通块之外出现了,那么一样直接舍去该方案。
具体的,我们可以用一个队列维护考虑集合内颜色出现的位置;每次弹出队首,并向 \(u\) 方向(父亲方向)扩展一步,再将父亲颜色的所有位置也加入队列即可。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 |
|