关于动态点分治实现的一个细节
查看原帖
关于动态点分治实现的一个细节
123294
Alex_Wei楼主2021/12/11 11:45

众所周知,在进行动态点分治时常常需要存储以某个结点为根时的子树信息。当信息与深度(即每个点到根的距离)有关时,如本题,那么为动态数组开大小的时候不能为了省常数而开 “从当前根在点分树上的父亲向当前根方向 dfs 寻找重心时,以所进入的儿子为根的子树最大深度"。原因如下:

假设当前根为 rr,我们要寻找 rr 的某个儿子 ii 的子树的重心 rr' 并为 rr' 设置 vector 大小。因为ii 为根时子树最大深度可能小于以 rr' 为根时的子树最大深度,一个扫把就能卡掉(如下图),导致 rr' 子树内节点与 rr' 之间的原树距离大于我们设置的 vector 大小,发生 RE / WA。

oTuoQg.png


错误写法大概就是在找重心的过程中求 maxdep 数组,并以当前根儿子的 maxdep 作为子树以 rr' 为根时的最大深度,如:

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 作为大小也应再从找到的 rr' 进行一遍 dfs,不如直接用子树大小来得方便。

不过我觉得应该没有人会和我一样蠢到犯这个错误

2021/12/11 11:45
加载中...