概述
( ext{dsu on tree}) 是一种思想,建立在轻重链剖分之上对暴力进行合并的思想。名为树上启发式合并,还是因为它每次保留了重儿子的信息(可以看成是一种合并)。一般用于处理无修改操作的子树问题。
思路
- 轻重链剖分出重儿子。
- 在树上统计答案。(对于当前访问的 (x))
- 在 (x) 的轻儿子处统计答案,不保留其贡献。
- 在 (x) 的重儿子处统计答案,并保留其贡献。
- 将 (x) 的轻儿子的贡献加入。
- 若 (x) 是某个点的重儿子,保留其贡献,否则不保留其贡献。
时间复杂度及其证明
时间复杂度是 (O(n log n(f(n)+g(n)))) 的,其中 (f(n)) 与 (g(n)) 分别为统计答案的时间复杂度与加入贡献的时间复杂度。
证明也很简单。我们知道一个点到根的路径上只会有 (log n) 条轻边。那么,一个点在加入贡献时被它的祖先所访问只会有两种情况:通过一条轻边或通过一条重边。通过轻边会访问 (log n) 次,而通过重边只会访问 (1) 次。故时间复杂度就是 (log) 的。
trick
一般可以在树上使用桶来存储一些子树相关的信息,或者使用其他数据结构。思路大体还是和暴力一样,只不过我们希望这个暴力跑的尽可能快(逃
子树相关的信息可以直接维护,但路径相关的信息因为一定在 ( ext{lca}) 处统计所以我们需要一些技巧。考虑一种处理方式:通过 ( ext{dfs}) 序保证不会出现在非 ( ext{lca}) 处统计答案。
例题
CF600E,CF570D,CF208E:使用 ( ext{dsu on tree}),开桶统计信息即可。
CF375D:开 ( ext{BIT}) 维护信息或直接维护信息,前者 (O(n log^2 n)) 后者 (O(n log n))。
[IOI2011]Race:开 ( ext{map}) 维护从 (1 o x) 的路径长度对应的最小深度,统计答案即可。
gym102832F:套路的,处理这种 (ioplus j) 的信息,拆位开桶维护即可。
模板代码
inline void dfs1(int x,int fa) {
sz[x]=1; son[x]=0;
for(register int i=h[x];i;i=ver[i]) {
int y=to[i]; if(y==fa) continue;
dfs1(y,x); sz[x]+=sz[y];
if(sz[son[x]]<sz[y]) son[x]=y;
}
}
inline void upd(int x,int fa,int d) {
...
for(register int i=h[x];i;i=ver[i]) {
int y=to[i]; if(y==fa||y==Son) continue;
upd(y,x,d);
}
...
}
inline void dfs2(int x,int fa,int opt) {
for(register int i=h[x];i;i=ver[i]) {
int y=to[i]; if(y==fa||y==son[x]) continue;
dfs2(y,x,0);
}
if(son[x]) dfs2(son[x],x,1),Son=son[x];
upd(x,fa,1); Son=0;
...
if(opt==0) upd(x,fa,-1);
}