树的重心(双dfs,换根)
思路:
基于树形 DP 的两次遍历(第一次dfs
计算以某个初始根(这里选了 1)为根时各子树的深度和与节点数,第二次zy
进行换根操作,更新每个节点作为根时的深度和)
换根原理:
更换主根,只需要对父节点进行运算即可,没必要再次循环;后面代码有详解:
#include <bits/stdc++.h>
using namespace std;
const int N=1e6+5;
long long n,d[N],p[N],o[N];//deep数组存储以每个节点为根时的总深度和,point数组存储子树大小
vector<vector<int>> a; // 邻接表存储树结构
// 第一次DFS:计算以节点1为根时,每个节点的子树大小和子树总深度
void dfs(int x,int pr){
p[x]=1; // 初始化当前节点的子树大小为1(仅包含自身)
d[x]=0; // 初始化当前节点的子树总深度为0
// 遍历当前节点的所有邻接节点
for(auto i:a[x]){
if(i!=pr){ // 避免处理父节点(防止重复计算)
dfs(i,x); // 递归处理子节点
// 更新当前节点的子树总深度:
// d[i]是子节点i的子树总深度,p[i]是子节点i的子树大小
// 每个子节点i的子树中的所有节点到当前节点的距离都要+1,因此增加p[i]
d[x]+=d[i]+p[i];
// 更新当前节点的子树大小:加上子节点i的子树大小
p[x]+=p[i];
}
}
}
// 第二次DFS:换根DP,计算以每个节点为根时的总深度和
void zy(int x,int pr){
// 遍历当前节点的所有邻接节点
for(auto i:a[x]){
if(i!=pr){ // 避免处理父节点
// 核心换根公式:
// d[x]是以x为根的总深度和
// n-p[i]是除了i的子树外的节点数
// p[i]是i的子树大小
// 当根从x变为i时,i的子树中所有节点的深度减少1(距离根更近了)
// 其余节点的深度增加1(距离根更远了)
d[i]=d[x]+n-p[i]-p[i];
// 递归处理子节点,继续换根过程
zy(i,x);
}
}
}
int main(){
cin>>n; // 输入节点数
a.resize(n + 1); // 调整邻接表大小
// 输入n-1条边,构建树
int m,l;
for(int i=1;i<n;i++){
cin>>m>>l;
a[m].push_back(l);
a[l].push_back(m);
}
// 第一次DFS:以节点1为根,计算初始子树信息
dfs(1,0);
// 第二次DFS:换根DP,计算每个节点作为根时的总深度和
zy(1,0);
// 寻找总深度和最大的节点
int mi=n;
for(int i=n-1;i>0;i--){
if(d[i]>=d[mi])mi=i;
}
// 输出结果
cout<<mi<<endl;
return 0;
}