树的重心
定义
如果树上的某一个节点的最大子树的节点数最小,那么这个节点就是树的重心。
性质
- 删除重心后所得的所有子树,节点数不超过原树的1/2,一棵树最多有两个重心;
- 树中所有节点到重心的距离之和最小,如果有两个重心,那么他们距离之和相等;
- 如果两棵树通过一条边合并,新的重心在原树的两个重心的路径上;
- 树删除或添加一个叶子节点,重心最多只移动一条边;
树重心的求法
从定义出发找重心:这个点作为根时,它的最大子树的节点个数不能大于树的全部节点数的一半。
寻找一棵无根树的重心时,一般就先随便找一个点作为根,然后从这个点开始往下走dfs,然后整个dfs走完了之后,就能够找到树的重心了。
本质上是这样的,假设现在有一棵无根树,我们先随便选点1作为根,那么整个树的结构是这样的
一般来说我们认为以3、4、5为根节点的三棵子树就是节点2所拥有的三棵子树,但是这个情况是我们选择1作为根节点形成的树结构,如果是下面这种情况,那么以1为根的子树也算是节点2之下的
所以,对于一个节点,需要从它下面的和“上面”的子树中,找出包含节点数最多的子树,并且它的节点数不能超过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;
}
参考