换根dp

一.基本概念

换根DP,又称二次扫描 ,是树形DP的一种扩展方法。它适用于无固定根节点的树,并且根节点的选择会影响某些计算结果(如子树深度和、节点权值和等)的问题。普通树形DP通常只计算以某个固定节点为根的结果,而换根DP需要计算每个节点作为根时的答案。

二.例题解析

POI2008 STA-Station

朴素做法

状态定义:\(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)做法

3.拓展练习

CF1324F Maximum White Subtree

P1395 会议

P2986 USACO10MAR Great Cow Gathering G

U656783 有根点权树