众所周知,在进行动态点分治时常常需要存储以某个结点为根时的子树信息。当信息与深度(即每个点到根的距离)有关时,如本题,那么为动态数组开大小的时候不能为了省常数而开 “从当前根在点分树上的父亲向当前根方向 dfs 寻找重心时,以所进入的儿子为根的子树最大深度"。原因如下:
假设当前根为 r,我们要寻找 r 的某个儿子 i 的子树的重心 r′ 并为 r′ 设置 vector
大小。因为以 i 为根时子树最大深度可能小于以 r′ 为根时的子树最大深度,一个扫把就能卡掉(如下图),导致 r′ 子树内节点与 r′ 之间的原树距离大于我们设置的 vector
大小,发生 RE / WA。
错误写法大概就是在找重心的过程中求 maxdep
数组,并以当前根儿子的 maxdep
作为子树以 r′ 为根时的最大深度,如:
void findr(int id, int f, int tot) { // tot 是传入的整棵子树大小
mxd[id] = sz[id] = 1, mx[id] = 0;
for(int it : e[id]) if(!vis[it] && it != f)
findr(it, id, tot), sz[id] += sz[it], cmax(mx[id], sz[it]), cmax(mxd[id], mxd[it] + 1);
if((mx[id] = max(mx[id], tot - sz[id])) < mx[R]) R = id;
}
然后在分治函数中写:
void divide(int id, int f) {
vis[id] = 1, fa[id] = f, C[0][id].resize(sz[id] + 1), C[1][id].resize(sz[id] + 1);
for(int it : e[id]) if(!vis[it]) R = 0, findr(it, id, sz[it]), sz[R] = dep[it] + 1 /*注意这里*/, divide(R, id);
}
实际上正确的写法应该是
void divide(int id, int f) {
vis[id] = 1, fa[id] = f, C[0][id].resize(sz[id] + 1), C[1][id].resize(sz[id] + 1);
for(int it : e[id]) if(!vis[it]) R = 0, findr(it, id, sz[it]), sz[R] = sz[it] + 1, divide(R, id);
}
综上,即使要用 maxdep
作为大小也应再从找到的 r′ 进行一遍 dfs,不如直接用子树大小来得方便。
不过我觉得应该没有人会和我一样蠢到犯这个错误。