Loading

[笔记] 树分治

点分治

适合处理大规模的树上路径信息问题。

考虑随意选择一个节点作为根节点。

所有完全位于其子树中的路径可以分为两种:

  • 经过当前根节点的路径,这种路径又可以分为两种:
    • 以根节点为一个端点的路径;
    • 两个端点都不为根节点的路径(可以由前者合并得到);
  • 不经过当前根节点的路径。

所以可以在当前根节点只解决经过它的路径,然后递归解决问题。

若每次选重心为根,就只递归 \(\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);
	}
}
posted @ 2022-03-11 22:03  IrisT  阅读(42)  评论(0编辑  收藏  举报