题目
树的重心
时间限制: 1 Sec 内存限制: 128 MB
题目描述
给出一个连通的无向图,它有N 个顶点和 N-1 条边 ,这显然是一棵树。现在需要找到这棵树的重心。
为了定义树的重心,需要给树的每一个顶点赋上一个权值。考虑顶点k。如果从图中删除k号顶点(连带的边也一起被删除),剩下的图将只有 N-1 个顶点而且可能由多个连通分量组成。显然每一个连通分量还是一棵树。那么k号顶点的权值就是删除它以后剩下的连通分量中顶点数最多的顶点个数。
这 N 个顶点中权值最小的就是重心。
例如,在如图所示的树中,删除结点2,整棵树分离成3个子树:(3)、(4)和 (1,5,6,7),最大连通分量的值为4
如果删除结点1,树将分离成2个子树(2,3,4)和(5,6,7),最大连通分量的值为3
枚举所有结点后发现,删除1的结果是最小的,因此1是这棵树的重心。
树的重点不一定是唯一的。
输入
第一行是整数 N (1<=N<=16 000)。接下来 N-1 行每行两个整数, a 和 b,用空格隔开,表示顶点 a 和 顶点 b之间有一条边。
输出
第一行输出两个整数,最小的权值和重心的个数
第二行按递增顺序输出这些重心的编号。数据之间用一个空格隔开。
样例输入
7
1 2
2 3
2 4
1 5
5 6
6 7
样例输出
3 1
1
分析
重心,简而言之就是在树上找一点,把树分成几部分,使得最大部分最小。
很明显要知道每个子树的大小,定义
Size(i)
S
i
z
e
(
i
)
表示以
i
i
为根的子树的大小(包括它自己)。于是。
这样便可以
O(N)
O
(
N
)
遍历得出
d
d
数组。
得到以后,枚举每一个点,就能得到这个点删除后各部分的大小。例如删除
i
i
后,最大部分的大小就是,
N−Size[i]
N
−
S
i
z
e
[
i
]
即除去以
i
i
<script type="math/tex" id="MathJax-Element-1210">i</script>为根的子树剩下的大小。
代码
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
#define MAXN 16000
#define INF 0x7fffffff
vector<pair<int,bool> > G[MAXN+5];
int Size[MAXN+5],w[MAXN+5];
int N;
void GetSize(int u,int fa){
Size[u]=1;//初值(u自己)
int sz=G[u].size();
for(int i=0;i<sz;i++){
int v=G[u][i].first;
if(v!=fa){
G[u][i].second=1;//second==1表示G[u][i]是u的儿子,反之则是u的父亲
GetSize(v,u);
Size[u]+=Size[v];
}
}
}
int main(){
scanf("%d",&N);
for(int i=1;i<N;i++){
int u,v;
scanf("%d%d",&u,&v);
G[u].push_back(make_pair(v,0));
G[v].push_back(make_pair(u,0));
}
GetSize(1,-1);
int Ans=INF;
for(int i=1;i<=N;i++){
w[i]=N-Size[i];
int sz=G[i].size();
for(int j=0;j<sz;j++)
w[i]=max(w[i],G[i][j].second*Size[G[i][j].first]);
//如果G[i][j]是i的父亲就不能统计它的Size
Ans=min(Ans,w[i]);
}
printf("%d",Ans);
int sum=0;
for(int i=1;i<=N;i++)
if(w[i]==Ans)
sum++;
printf(" %d\n",sum);
bool f=1;
for(int i=1;i<=N;i++)
if(w[i]==Ans){
if(f) printf("%d",i),f=0;
else printf(" %d",i);
}//输出重心
}