一、题目描述
给定一颗树,树中包含 n 个结点(编号 1∼n )和 n−1 条无向边。
请你找到树的重心,并输出将重心删除后,剩余各个连通块中点数的最大值。
重心定义:重心是指树中的一个结点,如果将这个点删除后,剩余各个连通块中点数的最大值最小,那么这个节点被称为树的重心。
输入格式:
第一行包含整数 n ,表示树的结点数。
接下来 n−1 行,每行包含两个整数 a 和 b,表示点 a 和点 b 之间存在一条边。
输出格式:
输出一个整数 m,表示将重心删除后,剩余各个连通块中点数的最大值。
数据范围:
1
≤
n
≤
1
0
5
1\leq n\leq 10^{5}
1≤n≤105
输入样例:
9
1 2
1 4
1 5
2 7
2 8
4 9
5 3
5 6
输出样例:
3
二、题目分析
题目要求我们找到树的重心,输出删除这个重心后,剩余各个连通块中,结点数的最大值。
因此,我们可以通过遍历树中的每一个结点,求出删除该节点后,各个连通块中的结点数。
我们以输入样例中的树(下图所示)作为例子进行分析。
删除结点5后,形成三个连通块,如下图:
删除结点1后,形成三个连通块,如下图:
我们可以发现,删除某个结点后剩余的连通块数量,实际上等于与该结点相连的结点数。同时,连通块1、2、3也可以视作所删除结点的子树。
因此,树的重心问题,可以转化为求子树的结点个数问题。我们可以用DFS(深度优先搜索)来求解。
那么我们具体应该如何求解?
1.计算num值
在一棵树中,每一个结点都可以作为根节点。在本例中,我们以1
作为根结点,于是问题转化为:以1
为根结点的情况下,求出每个结点所有子树的结点个数加上自身,我们记为num
。即num=子树1结点个数+子树2结点个数+...+1
。
下图的红色数字表示每个结点的num
值。
2.遍历结点,计算剩余连通块的结点数
为了方便表述,我们将待删除的结点记为i
,与i
连接的结点记为j
,树的总结点个数记为n
。
由于删除结点i
后剩余的连通块数量,等于与i
相连的结点数。因此我们可以分两种情况进行讨论:
j
是i
的子节点,则j
所在连通块的结点数量为num[j]
;j
是i
的父结点,则j
所在连通块的结点数量为n-num[i]
;
遍历所有结点,得到的结果如下表格所示:
三、代码示例
#include<iostream>
#include<cstring>
using namespace std;
const int N=100010;
int n;
bool state[N]; //标记当前结点是否被遍历过
int num[N]; //结点i及其它所有子树的结点个数
//链表相关变量的定义
int h[N],e[N*2],ne[N*2],idx; //分别表示头结点、结点的值、结点指向的下一个结点,idx不知道
void add(int a,int b){ //新增一个结点,将其放到头结点h[a]的后边,该结点的值为b,
e[idx]=b;
ne[idx]=h[a];
h[a]=idx;
idx++; //idx是新增的结点下标
}
//计算num值(结点u的所有子树结点个数+1)
void dfs(int u){
state[u]=true; //当前结点已经走过啦
num[u]=1; //u结点自身
//如果u为叶子结点,则下边的循环不会进行
for(int i=h[u];i!=-1;i=ne[i]){ //遍历与结点u相连的所有结点
int id=e[i]; //当前遍历的结点编号为id
if(!state[id]){ //如果结点id没有遍历过
dfs(id); //计算结点id的num值
num[u]+=num[id]; //累加所有子树的结点个数
}
}
}
int solve(){
int ans=N; //最终的答案
for(int i=1;i<=n;i++){ //遍历所有结点,将待删除的结点记为i
int res=0;
//遍历与结点i相连的所有结点j,得到删除每个结点i后,连通图中结点数的最大值res
for(int k=h[i];k!=-1;k=ne[k]){ //注意,头结点是一个指针
int j=e[k]; //j为当前结点的编号
if(num[j]<num[i]) //j是i的子节点时
res=max(res,num[j]);
if(num[j]>num[i])
res=max(res,n-num[i]); //j是i的父节点时
}
ans=min(ans,res);
}
return ans;
}
int main(){
cin>>n;
memset(h,-1,sizeof h); //初始化链表
for(int i=0;i<n-1;i++){
int a,b;
scanf("%d%d",&a,&b);
add(a,b),add(b,a);
}
dfs(1); //从结点1开始遍历
cout<<solve(); //输出结果
return 0;
}
代码中用链表存储树。出于懒惰,就不写该数据结构在使用过程中的亿些细节了。