【笔记】二次换根
该算法可以两次 DFS 求出以每个节点为根所得树的深度。
算法实现
朴素做法:枚举每个节点作根的情况进行 DFS,\(O(n^2)\)。
要想优化,就必须避免改变树的结构。我们发现,子树 \(u\) 的节点在以 \(u\) 为根的大树中到根 \(u\) 的距离都减少了一,其他节点因为要经过 \(u\) 的父节点到达 \(u\),所以距离都加了一。
于是我们使用树形 DP 求出递推式。有两种写法:
1. 统计以 \(u\) 为根子树外的节点
第一次 DFS,求出以每个节点为根的子树到这个节点的深度和 \(down\)。
第二次 DFS,可以发现以 \(u\) 为根构造的树的深度和为 \(down_u\) 加上以 \(u\) 为根子树外的节点深度和 \(up_u\)。我们使用树形 DP 求出 \(up_u\) 和以 \(u\) 为根的子树节点树 \(c_u\),最后统计 \(down_u+up_u\) 的最大值。
\(up_u\) 的递推式可以这么求:\(u\) 父亲节点的 \(up\) \(+\) 父亲节点的 \(down\)(即以父亲为根树的深度总和)\(-\) \(u\) 节点的 \(down\) (删去 \(u\) 子树)\(-\) \(c_u\)(\(u\) 节点子树的节点深度全部减一)\(+\) \(n-c_u\)(\(u\) 节点子树以外的节点深度全部加一)
void dfs(int root,int fa){
c[root]=1;
dep[root]=dep[fa]+1;
for (int i=h[root];i;i=e[i].nxt) {
int v=e[i].to;
if (v!=fa) {
dfs(v,root);
c[root]+=c[v];
in[root]+=in[v]+c[v];
}
}
}
void dp(int root,int fa){
for(int i=h[root];i;i=e[i].nxt){
int v=e[i].to;
if(v!=fa){
f[v]=f[root]+in[root]-in[v]-c[v]+n-c[v];
dp(v,root);
}
}
}
2. 统计以 \(u\) 为根的深度总和
第一次 DFS 只维护 \(c_u\) 即可。
第二次 DFS 求出以 \(u\) 为根的深度总和,即 \(u\) 子树集体深度减一、非 \(u\) 子树集体深度加一。\(dp_v=dp_u-c_v+n-c_v\)。
void dfs(int root,int fa){
c[root]=1;
dep[root]=dep[fa]+1;
for (int i=h[root];i;i=e[i].nxt) {
int v=e[i].to;
if (v!=fa) {
dfs(v,root);
c[root]+=c[v];
}
}
}
void dp(int root,int fa){
for(int i=h[root];i;i=e[i].nxt){
int v=e[i].to;
if(v!=fa){
f[v]=f[root]-c[v]+n-c[v];
dp(v,root);
}
}
}

浙公网安备 33010602011771号