[笔记] 树分治
点分治
适合处理大规模的树上路径信息问题。
考虑随意选择一个节点作为根节点。
所有完全位于其子树中的路径可以分为两种:
- 经过当前根节点的路径,这种路径又可以分为两种:
- 以根节点为一个端点的路径;
- 两个端点都不为根节点的路径(可以由前者合并得到);
- 不经过当前根节点的路径。
所以可以在当前根节点只解决经过它的路径,然后递归解决问题。
若每次选重心为根,就只递归 \(\log\) 层,时间复杂度 \(O(n\log n)\).
点分树
用来解决 带点权/边权修改 的树上路径信息统计问题。
考虑在点分治中作为分治中心的节点,它们可以构成一个树形结构,也就是点分树.
点分树中同一层的结点所代表的连通块的大小总和是 \(n\) 的。
这意味着,点分治的时间复杂度是与点分树的深度相关的,也就是 \(\log\) 的。
于是查询和修改时,我们可以直接在点分树上暴力跳父亲,复杂度能得到保证。
同时,查询和修改的实现,往往是在每个点用数据结构储存整个子树中所有节点的信息。
注意
在点分树上,一个结点在其点分树上的祖先结点的信息中可能会被重复计算,需要消去影响。
一般的方法是对于一个连通块用两种方式记录:一个是其到分治中心的距离信息,另一个是其到点分树上分治中心父亲的距离信息。
// 建树代码
void dfs(int x, int fx){
siz[x] = 1, mx[x] = 0;
for(int i = head[x]; i; i = e[i].nx){
int y = e[i].to; if(y == fx || vis[y]) continue;
dfs(y, x), siz[x] += siz[y], mx[x] = max(mx[x], siz[y]);
}
mx[x] = max(mx[x], all - siz[x]);
if(mx[x] < mx[Rt]) Rt = x;
}
void get(int x, int fx){
dis[x] = dis[fx] + 1, v[Rt].pb({x, dis[x]});
for(int i = head[x]; i; i = e[i].nx){
int y = e[i].to; if(y == fx || vis[y]) continue;
get(y, x);
}
}
void solve(int x){
vis[x] = 1, dis[0] = -1, get(x, 0);
for(int i = head[x]; i; i = e[i].nx){
int y = e[i].to; if(vis[y]) continue;
Rt = 0, all = siz[y], dfs(y, 0), dfs(Rt, 0);
fa[Rt] = x, solve(Rt);
}
}