题目描述
给定一颗树,树中包含 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
前置知识
题目分析
在树中删除一个点后,将会构成一个森林,在森林中将会拥有一个结点最多的树,即最大连通块。求树的重心相当于就是在树中删除点的情况下会出现不同的森林,找到一种森林,该森林中最多结点的树比其余情况森林中最多结点的树的结点数最小,即最小的最大连通块。
首先,从某一结点u出发开始寻找,以结点u进行划分,将会得到以u为根的子树(下半部分)和上半部分。再来求出以u根的树中各个子树的结点个数,并取其中结点个数最多的子树,记其中的结点个数为res。
然后,再对上半部分。设图中结点个数为size,在第一步求各子树结点个数时,将各子树的结点个数累计求和并加上根节点,可得到以u为根的树中结点数量为sum,则可求得结点u的上半部分结点数为size - sum。
然后将res和上半部分进行比较找出具有结点个数最多的那棵树,即max(res, size - sum),便可得到删除u后这种情况下,所得到的最大连通子图。
以此方式,依次探索图中各个节点,将会得到各个最大连通子图。从中取结点个数最小的最大连通子图,即为所求。
树相当于是一种无环连通图,因此可用图的方式对其进行处理。
算法实现
#include <stdio.h>
const int N = 1e5 + 10, M = N * 2;
int n, ans = N; // ans记录剩余各连通块中数的最大值
int h[N], e[M], ne[M], idx; // 邻接表
bool visit[N]; // 记录i是否被搜过
int max(int a, int b) { return a > b ? a : b; }
int min(int a, int b) { return a < b ? a : b; }
void add(int a, int b){ // 头插法,邻接表存边
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
int dfs(int u){
int res = 0, sum = 1; // res记录u的最大子树的节点数,sum记录以u为根的树中的所有的节点数(从下开始去除上一半,n-sum表示u上面部分的节点数,初始化为1表示当前只有结点自身)
visit[u] = true; // 标记u这个点被搜过
for(int i = h[u]; i != -1; i = ne[i]){ // i是边的编号
int j = e[i]; // j是u的邻接点
if(!visit[j]){ // 如果没有被搜过
int s = dfs(j); // s是以j为根的子树的结点数
res = max(res, s); // 记录u的最大子树的结点
sum += s; // 累加u的各子树的结点数
}
}
// 由于深度遍历后续结点后,之前的结点已被标记为访问过,因此需要用n-sum来获取以上一个节点为根的子树结点个数。 max(res, n - sum)
// 更新答案,取各连通块中最大值进行比较得出的最小值
ans = min(ans, max(res, n - sum));
return sum;
}
int main(){
scanf("%d", &n);
for(int i = 0; i <= n; i++) h[i] = -1; // 初始化为头结点都指向-1(空指针)
for(int i = 0; i < n - 1; i++){ // 树中是不存在环的,对于有n个节点的树,必定是n-1条边
int a, b; scanf("%d%d", &a, &b);
add(a, b); add(b, a); // 无向图由a可到b,由b可到a
}
dfs(1); // 任取一点开始搜索
printf("%d", ans);
return 0;
}