一.基本概念
换根DP,又称二次扫描 ,是树形DP的一种扩展方法。它适用于无固定根节点的树,并且根节点的选择会影响某些计算结果(如子树深度和、节点权值和等)的问题。普通树形DP通常只计算以某个固定节点为根的结果,而换根DP需要计算每个节点作为根时的答案。
二.例题解析
朴素做法
状态定义:\(fx\)代表以\(x\)为根的子树所有节点的深度之和。
遍历每一个节点,让每一个节点当一次根节点,算出当时的答案
时间复杂度:\(O(n^2)\),50pts
cpp
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int n,siz[N],f[N],maxn=-1,id;
vector<int>e[N];
inline void dfs(int u,int fa){
siz[u]=1;
for(auto v:e[u]){
if (v==fa)continue;
dfs(v,u);
siz[u]+=siz[v];
f[u]+=siz[v]+f[v];
}
}
signed main(){
cin>>n;
for(int i=1;i<=n-1;i++){
int u,v;
cin>>u>>v;
e[u].push_back(v);
e[v].push_back(u);
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++)siz[j]=0,f[j]=0;
dfs(i,0);
if (f[i]>maxn){
maxn=f[i];
id=i;
}
}
cout<<id;
return 0;
}
//暴力O(n^2)做法
换根dp做法
分析
(以下图片均为求\(5\)为根节点时的做法)
步骤1.对于任意节点\(x\),当其作为根节点时,深度由两部分组成
①以\(x\)为根节点的子树深度和。
②除去①后的其他节点的深度和。

步骤2.令\(1\)号节点为根节点,跑一遍 dfs ,求出\(f\[\]\),这样步骤\(1\)中的第一部分,即以\(x\)为根的子树的深度和就是\(fx\)
步骤3.现在要求步骤\(1\)中第二部分的值,该部分的值也有三个小部分组成
设\(x\)的父节点为\(fa\)
①除了以\(fa\)为根节点的子树外,其他节点到\(fa\)的深度和
②\(x\)的兄弟节点到\(fa\)的深度和
③进行完前①②后,除了以\(x\)为根节点的子树外,其他所有节点均已到达\(fa\),然后从\(fa\)像\(x\)转移

步骤4:状态定义:\(fx\)代表以\(x\)为根的子树所有节点的深度之和,\(gx\)表示除了以\(x\)为根的子树外其他节点到\(x\)的深度和
步骤5:状态转移方程
- 步骤3中的①:\(gfa\)
- 步骤3中的②:\(ffa-(fx+sizex)\),解释:\(fa\)与\(x\)连了一条边,深度\(+1\),所以对于以\(x\)为根的子树,相当于所有点深度都加了\(1\),即为\(sizex\)
- 步骤3中的③:\(n-sizex\),解释:\(fa\)与\(x\)连了一条边,深度\(+1\),相当于以\(x\)为根的子树以外的所有节点的深度都加了\(1\),即为\(n-sizex\)
以上三部分相加,即:
\(gx=gfa+ffa-(fx+sizex)+n-sizex\)
节点\(x\)作为根的深度和即为\(fx+gx\),遍历每个节点,求最大值即可
时间复杂度\(O(n)\) 100pts
cpp
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=5e4+5;
int n,siz[N],f[N],g[N],minn=1e18,id;
vector<int>e[N];
inline void dfs1(int u,int fa){
siz[u]=1;
for(auto v:e[u]){
if (v==fa)continue;
dfs1(v,u);
siz[u]+=siz[v];
f[u]+=siz[v]+f[v];
}
}
inline void dfs2(int u,int fa){
g[u]=g[fa]+f[fa]-(f[u]+siz[u])+n-siz[u];
for(auto v:e[u]){
if (v==fa)continue;
dfs2(v,u);
}
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n;
for(int i=1;i<=n-1;i++){
int u,v;
cin>>u>>v;
e[u].push_back(v);
e[v].push_back(u);
}
dfs1(1,0);
//第二遍dfs涉及到fa所以不能直接从1开始dfs,要从1的子节点开始遍历
//或者在dfs2函数中加入if (u==1)continue;
for(auto v:e[1]){
dfs2(v,1);
}
for(int i=1;i<=n;i++){
if (minn>(f[i]+g[i])){
minn=f[i]+g[i];
id=i;
}
}
cout<<id<<" "<<minn;
return 0;
}
//换根dpO(n^2)做法