Loading

树的重心

树的重心

定义

如果树上的某一个节点的最大子树的节点数最小,那么这个节点就是树的重心。

性质

  1. 删除重心后所得的所有子树,节点数不超过原树的1/2,一棵树最多有两个重心;
  2. 树中所有节点到重心的距离之和最小,如果有两个重心,那么他们距离之和相等;
  3. 如果两棵树通过一条边合并,新的重心在原树的两个重心的路径上;
  4. 树删除或添加一个叶子节点,重心最多只移动一条边;

树重心的求法

从定义出发找重心:这个点作为根时,它的最大子树的节点个数不能大于树的全部节点数的一半。

寻找一棵无根树的重心时,一般就先随便找一个点作为根,然后从这个点开始往下走dfs,然后整个dfs走完了之后,就能够找到树的重心了。

本质上是这样的,假设现在有一棵无根树,我们先随便选点1作为根,那么整个树的结构是这样的

image-20210206173334344

一般来说我们认为以3、4、5为根节点的三棵子树就是节点2所拥有的三棵子树,但是这个情况是我们选择1作为根节点形成的树结构,如果是下面这种情况,那么以1为根的子树也算是节点2之下的

image-20210206173435943

所以,对于一个节点,需要从它下面的和“上面”的子树中,找出包含节点数最多的子树,并且它的节点数不能超过N/2,这样的话这个点就是树的重心了。“上面”的子树节点咋找呢?用节点总数减掉“下面”的子树节点数再减掉1(所讨论的该节点)即可。

所以用数组s表示跑dfs的时候某个点与其下面的子树的节点和,而用数组w表示某个节点的最大子树所含的节点数。

代码模板

const int maxn = 15000;
vector<int> G[maxn]; //使用vector邻接表来存图
int cnt=0,s[maxn],w[maxn],centroid[maxn] = {0}; 

void dfs(int cur,int fa) {
	int siz = G[cur].size();
	s[cur] = 1;
	w[cur] = 0;
	for(int i=0;i<siz;i++){  //遍历当前节点cur的所有相邻节点
		int v = G[cur][i];
		if(v != fa) {
			dfs(v,cur);
			s[cur] += s[v];   //dfs返回的时候,s[cur]会加上它下方所有子树的节点数
			w[cur] = max(w[cur], s[v]); //w的值就是它下方最大子树的节点数
		}
	}
	w[cur] = max(w[cur], N - s[cur]);  //如果w的值比它上方的子树小,则再更改w的值。
	if( w[cur] <= N/2 )   //如果该层的w满足要求,则说明最大的子树都比N/2小了,其他肯定也比N/2小,满足作为树的重心的要求。
		centroid[cnt++] = cur;  //将cur,也就是节点的编号写入centroid数组存起来。
	return;
}

典型例题

https://vjudge.net/problem/POJ-2378

这个题的题意就是找出某个节点,使得去除这个节点之后,其他节点组成的连通分量的个数都没有大于N/2的,那么其实就是求树的重心。

个人解答:

#include<cstdio>
#include<iostream>
#include<vector>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn = 15000;
vector<int> G[maxn];

int cnt=0,x,y,N,s[maxn],w[maxn],centroid[maxn] = {0}; 

void dfs(int cur,int fa) {
	int siz = G[cur].size();
	s[cur] = 1;
	w[cur] = 0;
	for(int i=0;i<siz;i++){
		int v = G[cur][i];
		if(v != fa) {
			dfs(v,cur);
			s[cur] += s[v];
			w[cur] = max(w[cur], s[v]);
		}
	}
	w[cur] = max(w[cur], N - s[cur]);
	if( w[cur] <= N/2 ) 
		centroid[cnt++] = cur;
	return;
}

int main() {
#ifndef ONLINE_JUDGE
	//freopen("in.txt","r",stdin);
#endif
	scanf("%d",&N);
	for(int i=0;i<N-1;i++){
		scanf("%d%d",&x,&y);
		G[x].push_back(y);
		G[y].push_back(x);
	}
	dfs(1,-1);
	sort(centroid,centroid+cnt);
	if(cnt == 0)cout<<"NONE"<<endl;
	else 
		for(int i=0;i<cnt;i++) 
			cout<<centroid[i]<<endl;
	return 0;
}

参考

  1. https://oi-wiki.org/graph/tree-centroid/
  2. https://www.cnblogs.com/knife-rose/p/11258403.html
posted @ 2021-02-13 16:52  Kevin_Matrix  阅读(316)  评论(2编辑  收藏  举报