随记
树的重心
今天学到了一种新的找重心写法,不用额外增加数组。
void dfs(int u,int f){
siz[u]=val[u];
for(int i=head[u];i;i=nxt[i]){
if(to[i]==f) continue;
dfs(to[i],u);
siz[u]+=siz[to[i]];
}
if((siz[u]<<1)>=tot&&!wt) wt=u;
}
证明:
首先:节点 \(u\) 是重心当且仅当:删除 \(u\) 后得到的每一个连通块的权重都 ≤ tot/2。等价地,对任一子节点 v(\(u\) 的某个邻居),对应的块大小要 \(≤ tot/2\);其中一个块对应“除去 \(u\) 的子树”的那部分,其大小正好是 \(tot - siz[u]\)。
代码里选的节点选取条件 \(siz[u] * 2 >= tot\)。
那么 \(tot - siz[u] ≤ tot/2\) 自然成立(即“父侧/其余部分”的块不会超过一半),因此只需再检查 \(u\) 的每个孩子子树是否也 \(≤ tot/2\)。
接下来:在后序(对子节点递归完成后)检查 \(siz[u]\),并且用 &&!ans 保证只设置一次。因为是后序,树上越深的节点越早被检查(深节点的 \(dfs\) 先执行完返回),所以第一次满足 \(siz[...] >= tot/2\) 的节点,实际上是“最深的满足 \(siz >= tot/2\) 的节点”。
如果 \(u\) 的某个子节点 \(v\) 的子树大小 \(> tot/2\),那 \(v\) 必然也是满足 \(siz[v] ≥ tot/2\) 的节点,而且比 \(u\) 更深,按后序会先被发现并把 ans 设为 v(从而 u 不可能成为“第一次被设定”的节点)。因此,既然 \(u\) 被第一个设定为 \(ans\),说明它所有的子节点的子树大小都 \(< tot/2\)。也就保证了“删除 \(u\) 后任一子树块大小 \(≤ tot/2\)”。
因此 \(u\) 满足重心条件
结合第 \(2、3\) 点,\(u\) 的每一个分块(包括父侧)都 \(≤ tot/2\),因而 \(u\) 是重心。

浙公网安备 33010602011771号