树的重心与直径

树的重心与直径

树的重心

有以下三种定义方式:

  • 以某点为根,最大子树的节点数最小,该点为重心
  • 以某点为根,所有子树的节点数不超过总节点数的一半,该点为重心
  • 以某点为根,所有节点到这个根边数和最小,该点为重心

也存在以下这些性质:

  • 一棵树最多有两个重心,且这两个重心一定相邻
  • 给这棵树增加或减少一个叶子节点,转移后的重心最多只会移动一条边
  • 将两棵树合并后,新的重心一定在原来的两个重心的路径上
  • 如果树的边权都为正,那么所有节点到重心的边权和最小,且与边权的分布无关

求树的重心,可用树形DP的方式计算

计算重心的过程中,任取当前遍历到的节点 \(u\),如果 \(u\) 为重心,那么可以把以 \(u\) 为根的子树连通块分为两类:

\(u\) 的子树和 \(u\) 之上的连通块,例如在上图中以 \(6\) 为根节点,则第一类为连通块 \(\{1,2,3,4,5\}\) 第二类为 \(\{7\},\{8\},\{9\}\)

根据树的重心的第一个定义,我们需要使得 \(u\) 节点的最大子树的节点数与 \(u\) 节点之上的连通块的节点数的最大值最小

\(sum\)\(u\) 节点为根的子树的节点数之和,\(size\)\(u\) 节点最大子树的节点树

那么 \(u\) 之上的连通块的节点数为 \(n-size\)\(n\) 为所有节点总数,我们需要使这样一个表达式最小,即计算:

\[{\rm{min}_{u=1}^n}[{\rm{max}}(size_u,n-sum_u)] \]

int dfs(int x,int p){
	int size=0;//初始化
	int sum=1;//以u为根的节点总数包括根节点
	for (auto v:e[x]){
		if (v==p) continue;
		int res=dfs(v,x);//
		size=max(size,res);//回溯计算最大子树的节点数
		sum+=res;//回溯计算以u为根的节点总数
	}
	if (ans>max(size,n-sum)){
		ans=max(size,n-sum);
		st.clear();
		st.push_back(x);
	}else if (ans==max(size,n-sum))
		st.push_back(x);//如果有多个重心则记录
	return sum;//返回以这个节点为根的节点总数
}

每次回溯时,都上传 \(sum\) ,即向父节点转移以某个子节点 \(u\) 为根的节点总数

同时在父节点上计算他的最大子树的节点数,即 size=max(size,dfs(v,x))

树的直径

定义:树上任意两点之间最长的简单路径为树的直径

有两种求法为:两次DFS,树形DP

其中 DFS 的方法只能处理非负权树,树形DP的方法无法得到直径路径上的信息(只能得到直径的值)

存在以下性质:

  • 直径两端点一定是两个叶子节点
  • 对于两棵树,他们的直径分别被表示为 \({\rm{dis}}(x,y),{\rm{dis}}(u,v)\) ,用一条边将这两棵树连接后新树的直径一定是 \(\{x,y,u,v\}\) 其中的两个点形成的简单路径
  • 对于一棵树的,在他的某一点上新接一个叶子节点,那么最多只会改变树的直径的一个端点
  • 如果一棵树有多个直径,那么这些直径一定会有一部分在中间部分重合 (公共点或公共路径)

两次DFS:

第一次DFS找到离出发点最远的节点 \(c\) ,第二次DFS从 \(c\) 出发再到离 \(c\) 最远的节点,他们之间的简单路径距离即为树的直径

void dfs(int x,int p){
	for (auto it:e[x]){
		if (it.v==p) continue;
		d[it.v]=d[x]+it.w;//记录路径长度
		if (d[it.v]>d[c]) c=it.v;//更新最远点
		dfs(it.v,x);
	}
}

void solve(){
	int n;
	cin>>n;
	for (int i=1;i<n;i++){
		int u,v,w;
		cin>>u>>v>>w;
		e[u].push_back({v,w});
		e[v].push_back({u,w});
	}
	dfs(1,-1);
	d[c]=0;//第二次dfs从c出发
	dfs(c,-1);
	cout<<d[c]<<endl;
}

树形DP:

任取树上一点 \(u\)\(d_1,d_2\) 分别记录 \(u\) 的子树中的最长路径和次长路径,需要计算的树的直径为 \({\rm{max}_{u=1}^n}(d_{1}+d_2)\)

int dfs(int x,int p){
	int d1=0;
	int d2=0;
	for (auto it:e[x]){
		if (it.v==p) continue;
		int td=dfs(it.v,x)+it.w;//计算子节点的最远路径
		if (td>d1) d2=d1,d1=td;//更新最长路径和次长路径
		else if (td>d2) d2=td;
		ans=max(ans,d1+d2);//记录答案
	}
	return d1;//返回当前节点的最长路径
}


void solve(){
	int n;
	cin>>n;
	for (int i=1;i<n;i++){
		int u,v,w;
		cin>>u>>v>>w;
		e[u].push_back({v,w});
		e[v].push_back({u,w});
	}
	int x=dfs(1,-1);
	cout<<ans;
}
posted @ 2025-04-27 19:14  才瓯  阅读(65)  评论(0)    收藏  举报