树的重心
题目描述
核心思路
任取一节点u,若以u为重心,则分为两类:
- 一类是u的子树
- 另一类是u上面的部分
然后统计u的子树中最大的那一颗子树所含有的节点的数量,不妨设为 x x x,再统计u上面部分的节点数量,不妨设为 y y y,比较 x , y x,y x,y的最大值,就可以得到删除节点u后的最大数量。
比如对于节点 x 1 x_1 x1,设删除节点 x 1 x_1 x1后得到的最大数量是 y 1 y_1 y1;对于节点 x 2 x_2 x2,设删除节点 x 2 x_2 x2后得到的最大数量是 y 2 y_2 y2;$\cdots ; 对 于 节 点 ;对于节点 ;对于节点x_2 , 设 删 除 节 点 ,设删除节点 ,设删除节点x_n 后 得 到 的 最 大 数 量 是 后得到的最大数量是 后得到的最大数量是y_n$;
那么最终的答案就是 a n s = m i n ( y 1 , y 2 , ⋯ , y n ) ans=min(y_1,y_2,\cdots ,y_n) ans=min(y1,y2,⋯,yn)。
- 我们用变量cnt来记录u的最大子树所含有的节点个数(不包括节点u)
- 用变量sum来记录以u为根的子树的节点个数(包括节点u)
- 那么u上面部分的节点数量就是 n − s u m n-sum n−sum
- 对于删除节点u来说,得到的答案就是 a n s = m a x ( c n t , n − s u m ) ans=max(cnt,n-sum) ans=max(cnt,n−sum)
cnt和sum可以在向下遍历返回时统计和累加,设置bool数组vis[u]来标记节点u已经被搜索过了。
因为u是任取的一个节点,所以在遍历每个节点时都会得到一个ans,取最小值即可。
代码
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=100010,M=2*N;
int h[N],e[M],ne[M],idx;
bool st[N]; //用来判断某个节点是否被遍历过了
int n,m;
//最坏情况下是整个树就是最大数量
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; //标记节点u被搜索过了
int cnt=0; //记录u的最大子树中所含有的最大节点数量(不包括节点u)
//这里初始化为1是因为已经把节点u统计进去了
int sum=1; //记录以u为根的子树的所含有的全部节点数量(包括节点u)
for(int i=h[u];~i;i=ne[i])//i是边的编号
{
int j=e[i]; //j是u的邻接点
//说明j已经被遍历过了,避免向上查找
if(st[j])
continue;
//s是以j为根的子树的节点数量
int s=dfs(j);
//记录u的最大子树的节点数量
cnt=max(cnt,s);
//累加u的各个子树的节点数量
sum+=s;
}
ans=min(ans,max(cnt,n-sum)); //更新答案
//返回以某个节点为根的子树的节点数量
//注意这里并不是最终的返回值 只是在dfs中的返回值而已
//我们最终想要的答案是ans,而不是sum,sum只是帮助我们求出ans而已
return sum;
}
int main()
{
scanf("%d",&n);
memset(h,-1,sizeof h); //初始化表头
//对于树来说,有n个节点,那么就有n-1条边,循环只需要n-1次而不需要n次,要注意哦
for(int i=0;i<n-1;i++)
{
int a,b;
scanf("%d%d",&a,&b);
//建立无向图
add(a,b);
add(b,a);
}
//随便选取1~n中的某个节点开始dfs都可以
dfs(1);
printf("%d\n",ans); //输出答案
return 0;
}