题目描述
给定一颗树,树中包含 n 个结点(编号 1∼n)和 n−1 条无向边。
请你找到树的重心,并输出将重心删除后,剩余各个连通块中点数的最大值。
重心定义:重心是指树中的一个结点,如果将这个点删除后,剩余各个连通块中点数的最大值最小,那么这个节点被称为树的重心。
输入格式
第一行包含整数 nn,表示树的结点数。
接下来 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遍历图,dfs(u)返回的是以u为根节点的子树中节点的数目(包括u本身)
使用sum
表示以u为根的子树的大小,使用size
表示每个节点u删除后,剩余联通块中节点数目的最大值,则分两部分看
- 对于u的所有孩子节点(在u的列表中,并且还没有被访问过的节点)
j
:用对j进行dfs(j)
得到该子树的大小s,遍历所有节点求出size = max(s)
- 对u的父节点所在的连通块:其大小为
n - sum
所以 size = max(size, n-sum)
每次遍历所有u的孩子节点结束后,得到删除节点u之后,剩余连通块中节点数目的最大值:size
那么只需要维护一个全局变量ans
,求得所有节点size
的最小值 : ans = min(ans, size)
如图,从1开始遍历,当u=4这个节点的时候
其孩子子树大小分别为:2(3-9), 1(6)
其父节点所在的子树大小为:n-sum = 9 - 4 = 5 (1-2-8-5-7)
所以删除u之后剩余连通块中点的数目的最大值为:size = 5
遍历所有点的size就可得到最后的结果
代码
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 100010;
// 存图
int h[N], e[2*N], ne[2*N], idx = 0;
bool state[N]; // 标记每个点是否被访问过
int n;
int ans = N; // 最后的结果
// 建边
void add(int a, int b)
{
e[idx] = b;
ne[idx] = h[a];
h[a] = idx++;
}
// 返回以u为根的子树的大小,包含u
int dfs(int u)
{
state[u] = true; //访问了该点
// sum 表示以u为根的子树的大小
// size 表示以u为根的所有子树中节点数目的最大值
int sum = 1, size = 1;
// 枚举u所有的子树
for(int i = h[u]; i != -1; i = ne[i])
{
int j = e[i];
// 如果该子节点没有被访问过
if(!state[j])
{
// state[j] = true;
int s = dfs(j); // 该子树的大小
// state[j] = false;
sum += s;
size = max(size, s);
}
}
size = max(size, n - sum); //再看一眼u的父节点所在的子树大小n-sum
ans = min(ans, size);
return sum;
}
int main()
{
memset(h, -1, sizeof h);
scanf("%d", &n);
for(int i = 0; i < n-1; i++)
{
int a, b;
scanf("%d%d", &a, &b);
add(a, b);
add(b, a);
}
// state[1] = true;
dfs(1);
cout << ans << endl;
return 0;
}
时间复杂度
\(O(m+n)\)
参考文章
https://www.acwing.com/solution/content/4917/