定义及性质
定义1:找到一个点,删除它得到的森林中最大的子树节点数最少(也就是最大的连通块的点数最小),那么这个点就是这棵树的重心。
定义2:删除重心后得到的所有子树,其顶点数必然不超过n/2
性质1:树中所有点到某个点的距离和中,到重心的距离和是最小的;如果有两个重心,那么他们的距离和一样。
性质2:把两个树通过一条边相连得到一个新的树,那么新的树的重心在连接原来两个树的重心的路径上。
性质3:把一个树添加或删除一个叶子,那么它的重心最多只移动一条边的距离。

一般我们树形dp一下就能求出树的重心,我们用 dp[x] 表示 x 点的子树的节点数量,然后dfs一遍就可以了。


POJ 1655 Balancing Act

题目大意:求出树的重心,若有多个,输出最小的重心编号。

AC代码:

#pragma GCC optimize(2)
#include<cstring>
#include<iostream>
#include<algorithm>
#define int long long
using namespace std;
const int N=4e4+10;
int T,n,dp[N],res;//dp[x]为x的孩子的个数 
int head[N],nex[N],to[N],tot,id;
void add(int a,int b){
	to[++tot]=b; nex[tot]=head[a]; head[a]=tot;
}
void dfs(int x,int pre){
	int cnt=0,k=0;
	for(int i=head[x];i;i=nex[i]){
		if(to[i]==pre)	continue;
		dfs(to[i],x);
		dp[x]+=dp[to[i]]+1;	k=max(dp[to[i]]+1,k);
	}
	cnt=max(k,n-dp[x]-1);
	if(cnt<res||cnt==res&&x<id){
		res=cnt;	id=x;
	}
}
signed main(){
	cin>>T;
	while(T--){
		memset(dp,0,sizeof dp);
		cin>>n;	tot=0;	memset(head,0,sizeof head);	res=0x3f3f3f3f;
		for(int i=1;i<n;i++){
			int a,b;	cin>>a>>b;	add(a,b);	add(b,a);
		}
		dfs(1,-1);
		cout<<id<<' '<<res<<endl;
	}
	return 0;
}

POJ 3107 Godfather
题目大意:求出所有的树的重心编号,并按字典序排序输出。
AC代码:

#pragma GCC optimize(2)
#include<vector>
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
const int N=1e5+10;
int n,dp[N],res,idx;
int head[N],nex[N],to[N],tot;
struct node{
	int x,id;
}v[N];
int cmp(node a,node b){
	if(a.x==b.x)	return a.id<b.id;
	else	return a.x<b.x;
}
inline void add(int a,int b){
	to[++tot]=b; nex[tot]=head[a]; head[a]=tot;
}
void dfs(int x,int pre){
	int cnt=0;
	for(int i=head[x];i;i=nex[i]){
		if(to[i]==pre)	continue;
		dfs(to[i],x);
		dp[x]+=dp[to[i]]+1;	cnt=max(cnt,dp[to[i]]+1);
	}
	v[idx++]={max(cnt,n-1-dp[x]),x};
}
signed main(){
	scanf("%d",&n);
	for(int i=1;i<n;i++){
		int a,b;	scanf("%d %d",&a,&b);	add(a,b);	add(b,a); 
	}
	dfs(1,-1);
	sort(v,v+n,cmp);
	for(int i=0;i<n;i++){
		if(i&&v[i].x!=v[i-1].x)	break;
		printf("%d ",v[i].id);
	}
	puts("");
	return 0;
}