原题链接.
问题描述
给定一颗树,树中包含n个结点(编号1~n)和n-1条无向边。
请你找到树的重心,并输出将重心删除后,剩余各个连通块中点数的最大值。
重心定义: 重心是指树中的一个结点,如果将这个点删除后,剩余各个连通块中点数的最大值最小,那么这个节点被称为树的重心。
输入格式
第一行包含整数n,表示树的结点数。
接下来n-1行,每行包含两个整数a和b,表示点a和点b之间存在一条边。
输出格式
输出一个整数m,表示重心的所有的子树中最大的子树的结点数目。
数据范围
1≤n≤105
输入样例
9
1 2
1 7
1 4
2 8
2 5
4 3
3 9
4 6
输出样例:
4
问题分析
寻找一棵无根树的的重心,而重心的定义是删除这棵树的重心后剩余各个连通块中点数的最大值最小。
故先任选一个结点作为根,遍历整棵无根树的所有结点,并尝试删除此结点,求出所有联通块的最大值,最后找到这些最大值中的最小值就是答案。
而在dfs的过程中可以轻松地获取未被遍历过地子树的结点树,即某个连通块的数量,只需要在dfs的过程中求出整个子树的结点树即可,其中:
d [ i ] = ∑ d [ j ] + 1 d[i] = \sum{d[j]}+ 1 d[i]=∑d[j]+1
但是如何获取已经遍历过的结点所在的连通块的结点数量呢?
总结点数-d[j] 就是结果。
C++代码实现
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int N = 1e5+10,M = 2*N;//无向边是节点数二倍
//考了几天试,连静态邻接表都差点不会写了。。。
int e[M], ne[M], head[N];
bool st[N];
int idex = 0, ans = N, n;
//a, b之间加一条a->b的边
void add(int a, int b){
e[idex] = b;
ne[idex] = head[a];
head[a] = idex++;
}
//深度遍历节点x,求出节点x为根的节点数,并尝试删除x更新ans
//返回子树x的节点数
int dfs(int x) {
int m = 0;//各棵子树的最大节点数
int sum = 1;//记录整个子树的节点总数
st[x] = true;
//遍历直接相连的节点,求出各棵子树的节点数
for(int i = head[x]; i != -1; i = ne[i]) {
//只计算未访问过的节点
int j = e[i];
if(!st[j]) {
int temp = dfs(j);
m = max(m, temp);
sum += temp;
}
}
//最后一块连通块是包括其根节点的块,用总节点数减去其子树节点数即可
m = max(m, n-sum);
ans = min(ans, m);
return sum;
}
int main() {
cin>>n;
//建图
memset(head, -1, sizeof head);
for(int i = 0 ; i < n-1 ; ++ i ) {
int x, y;
scanf("%d%d", &x, &y);
add(x, y);
add(y, x);
}
//深搜求解
dfs(1);
cout<<ans<<endl;
return 0;
}