文章目录
1. dfs + 图
思路:
- 邻接表存树,注意无向边,正反存两次就行了。树的节点数是
n
,但是由于需要存正反两次边,作为无向图,故针对 值e[N*2]
、下一个ne[N*2]
,都需要开两倍空间进行存储。 - 考虑连通块种类
- 重心删除后的各个子树构成的连通块。
- 重心父节点所在的连通块。
- 我们可以通过
dfs
得到以任意节点作为根节点,它子树的节点数量。可以让dfs
就返回以当前重心作为树的节点数量,记为size(u)
,那么,dfs
下去再回溯上来就是重心的各个子节点作为根节点的点数量,那么在这些中取max
就是重心删除后各个子树构成连通块的最大值了,再拿这个最大值与n-size(u)
取个最大就是结果了当前u
节点所有连通块的最大结果了,当然最后再和每个节点的max
结果取个min
就行了。
只搜一个根,将所有的情况全部搜到,当然在此是无根树,搜任意的根均可。
代码中会给出详细注释,这也算是我入门图论的第一道实战题目!很综合的一道题目!
本题采用全局变量的方式来记录 dfs
的中间变量,即采用了 ans
这个值,注意,需要将其赋成最大值…一开始忘记了,结果取 min
后为 0。
代码:
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e5+5;
int n;
int h[N], e[N * 2], ne[N * 2], idx; // 邻接表
bool st[N]; // 判断节点是否已经被访问过了
int ans = N; // 最终结果,需要和每个节点返回的max取min
void add(int a, int b) { // 在邻接表中加上一条a->b的单向边
e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}
// 暴力枚举,深搜,找到树中每个节点的重心连通块最大值,每次取个min即可
// 返回以u为根的子树中点的数量
int dfs(int u) {
st[u] = true; // u已经被访问过了,即它被当过重心算过了
// sum表示当前子树的大小包括重心,所以初始化为1
// res表示删除当前节点后连通块的最大值
int sum = 1, res = 0;
for (int i = h[u]; i != -1; i = ne[i]) { // 遍历它的出边所有节点,选出最大的连通块res
int j = e[i]; // 链表节点对应到图中节点的编号,是u的所有出边
if (!st[j]) { // 出边未被遍历过,则说明它和它的子树都未被遍历过,在此保证只遍历一边
int s = dfs(j); // 递归得到节点j为根的子树各个连通块节点的数量
res = max(res, s); // 取max得到这条链上所有连通块的最大值
sum += s; // 加上s是对所有连通块求和,得到这个子树的所有节点数
}
}
res = max(res, n - sum); // 最大子树的连通块节点数res和重心父节点所在的连通块取max
ans = min(ans, res); // 至此,根节点的重心最大连通块数量已经求出,即res,每次res取min即可
return sum;
}
int main() {
memset(h, -1 ,sizeof h);
cin >> n;
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;
}