#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=100010,M=N*2;
int n;
int h[N],e[M],ne[M],idx;
bool st[N];
int ans=N;
void add (int a,int b)
{
e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
int dfs (int u)
{
st[u]=true;
int sum=1,res=0;
for (int i=h[u];i!=-1;i=ne[i])
{
int j=e[i];
if (!st[j])
{
int s=dfs(j);
res=max(res,s);
sum+=s;
}
}
res=max(res,n-sum);
ans=min(ans,res);
return sum;
}
int main()
{
cin >> n;
memset(h,-1,sizeof h);
for (int i=0;i<n-1;i++)
{
int a,b;
cin >> a >> b;
add (a,b),add (b,a);
}
dfs(1);
cout << ans << endl;
return 0;
}
dfs
函数接收一个整数参数 u
,它代表当前正在进行深度优先搜索的节点编号,函数最终会返回以该节点为根的子树包含的节点个数。
-
初始化操作
首先,将节点 u
标记为已访问,通过 st[u] = true
实现。这一步很关键,能避免在后续遍历过程中对同一个节点重复访问,防止陷入死循环。
接着初始化两个变量:
sum
用于统计以当前节点 u
为根的子树的节点总数,初始化为 1
,是因为要先把当前节点 u
自身算进去。
res
用于记录当前节点 u
的所有子树中节点个数的最大值,初始化为 0
,后续会不断更新这个值。
-
遍历邻接节点(子树遍历)
通过邻接表来遍历当前节点 u
的所有邻接节点(也就是它的子节点,对应以这些子节点为根的子树)。循环条件 i = h[u]; i!= -1; i = ne[i]
是标准的邻接表遍历方式,从当前节点 u
的边链表头开始,沿着链表逐个获取邻接节点,直到链表末尾(i == -1
)。
对于每个未被访问过的邻接节点 j
(通过 !st[j]
判断),递归调用 dfs
函数,即深入到以 j
为根的子树中去进行深度优先搜索,并得到以 j
为根的子树所包含的节点个数 s
。
然后更新 res
,取当前已得到的子树节点个数最大值与新得到的子树节点个数 s
中的较大值,即 res = max(res, s)
,这样就能保证 res
始终记录着已访问过的子树中节点个数最大的那个值。
同时,将以 j
为根的子树节点个数累加到 sum
中,即 sum += s
,以此统计出以当前节点 u
为根的整棵子树的节点总数。
-
计算当前节点删去后的最大部分节点数
在遍历完当前节点 u
的所有子树后,计算如果删除当前节点 u
,剩余部分(也就是除了已经统计过的子树节点之外的部分)的节点个数,即 n - sum
(n
是整棵树的节点总数),然后再取这个值和之前记录的最大子树节点个数 res
中的较大值,更新 res
。这样 res
此时就代表了删除节点 u
后,得到的所有 “部分”(子树或者剩余部分)中节点个数的最大值。
-
更新全局最小最大子树节点数并返回结果
首先,用当前节点 u
对应的最大部分节点数 res
和全局记录的最小最大子树节点数 ans
进行比较,取较小值更新 ans
,即 ans = min(ans, res)
,通过不断这样的更新,最终 ans
就能得到整棵树的重心对应的最大子树节点数的最小值。
最后,返回以当前节点 u
为根的子树的节点个数 sum
,方便上层调用(比如在递归过程中,父节点需要知道子树的节点个数来进行相应的计算等情况)。