题目:
思路:
大致思路: 如果有n个点,那么就是依次删除树中的某个节点找出剩下连通子图的数量的最大值,然后将删除这n个节点所得连通块的最大值再求一个max, 最后max出来的值就是树的重心
Q: 如何求出子树的大小?
A: 在递归到最深层的时候(以该点为树根往下没有出边了)可以返回以该节点为树根的整棵树的大小,
所以在最后一次递归返回的是以该点为树根的树的大小,大小为1, 而它的子树大小是0, 因此在某一层递归结束后的返回值并不包括它本身在内的大小, 而是它子树的大小. 某次递归结束后可以在回溯的过程将返回的子树的大小加起来(用于返回上层子树的大小),从而自底而上依次得到包括该点的子树的大小
Q: 如何得到删除某个节点后得到的最大的子树? 如何得到删除某个节点后的最大连通块的大小?
A: 以某个点作为树根,然后去遍历它的所有出边, 将每一次得到的结果都进行max操作, 当所有出边遍历完成(由于是递归的操作,所以在遍历到下个点的时候也会采用相同的方式去求子树大小),也就求出了以该点为树根的最大子树… 当求出以某个节点为树根的最大子树后, 再用 总节点数n-以该点为树根的子树大小, 就求得删除某个节点后的最大连通块
Q: 如何确保n个点每个点都被当成过树根,然后求出删除这个树根的最大连通块?
A: 其实在递归的过程中就已经求得了以某个点为根的最大子树,只需要在该点的出边结束后对所有的子树与n-最大子树求个max就得到了以该点为根的最大连通块.
注意: 由于该图是无向图, 在递归的时候不加处理就会反复往返陷入死递归,所以要用bool st[] 标记一下以某个节点为根, 为了在递归的时候只能向下走,不能回头
代码:
#include <iostream>
#include <cstring>
using namespace std;
const int N = 1e5 + 10, M = 2 * N;
int ans = N;
int n;
int h[N], e[M], ne[M], idx;
bool st[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 res = 0, sum = 1; //res在循环内部是以某点为根的最大子树的大小,外部是最大连通块的大小
for(int i = h[u]; i != -1; i = ne[i])
{
int j = e[i];
if(!st[j])
{
int s = dfs(j);
sum += s;
res = max(res, 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;
}