AcWing 846. 树的重心

题目

给定一颗树,树中包含 \(n\) 个结点(编号 \(1 \sim n\))和 \(n-1\) 条无向边。

请你找到树的重心,并输出将重心删除后,剩余各个连通块中点数的最大值。

重心定义:重心是指树中的一个结点,如果将这个点删除后,剩余各个连通块中点数的最大值最小,那么这个节点被称为树的重心。

输入格式

第一行包含整数 \(n\),表示树的结点数。

接下来 \(n-1\) 行,每行包含两个整数 \(a\)\(b\),表示点 \(a\) 和点 \(b\) 之间存在一条边。

输出格式

输出一个整数 \(m\),表示将重心删除后,剩余各个连通块中点数的最大值。

数据范围

\(1 \le n \le 10^5\)

输入样例

9
1 2
1 7
1 4
2 8
2 5
4 3
3 9
4 6

输出样例:

4

题解

C++ 代码

以链表的形式存储每个节点连的边
说是子树,实际上每一个节点连的数,都是以链表存储的,一个一个向下存
屏幕截图 2024-03-16 120812.png

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 1e5 + 10, M = N * 2; //以有向图的形式存储无向图,所以每个节点至多对应2n-2条边,M是每个节点 存储自身所连接的节点的链表 的数据范围

int n;
int h[N], e[M], ne[M], idx;
int ans = N; //表示重心的所有的节点中,节点的最大连通块数目 此处给ans赋初值N的含义是方便后面min(res, ans)取出最小值,故不能赋值为0
bool st[N]; //记录节点是否被访问过

//以单链表形式在节点a插入要连接的边b(类似哈希中的拉链法,在每一个节点下面挂一条链子)
void add(int a, int b)
{
    //存储连接的节点,在链表头插入元素的方法 e[]存储值 h[]和ne[]存储指针
    e[idx] = b;
    ne[idx] = h[a];
    h[a] = idx ++ ;
}

//返回以u为根的子树中节点的个数
int dfs(int u)
{
    int res = 0, sum = 1; //res存储删掉某个节点之后,最大的连通子图节点数 sum存储以u为根节点的树内所有节点数, 包括u自身,所以初值赋为1,
                          //将u也加进sum内计数的原因是后面res = max(res, n - sum)操作,n - sum操作就能直接计算以 u的父节点 为根节点的那一部分树的节点数量
    st[u] = true; //标记已经访问过u节点

    for (int i = h[u]; i != -1; i = ne[i]) //将i作为u所在链表的头节点,访问u的每个子节点
    {
        int j = e[i]; //j是u的一个连通节点 设置j的目的是方便下面判断j是否被访问过

        if (!st[j]) //当j未被访问过 st[j]是在执行dfs(j)时 执行到上面 st[u] = true;这一行时赋值的
        {
            int s = dfs(j); //u节点的单棵子树节点数(也就是以j节点作为父节点的树的所有节点数) dfs继续向下递归,最后一层一层返回,s就是子树j的节点数
            res = max(res, s); //利用max()输出当前这一层的子树的节点数最大值 (利用函数的递归实现)
            sum += s; //以j为根节点的树 的节点数
        }
    }

    //树的重心可能在u的子节点出现,即子树的最大连通块数量最小,在dfs(j)时ans会记录j树的最大连通块数量,从而确保最终一定会找到某一颗树的最大连通块数量最小并存储
    res = max(res, n - sum); //找到u的所有子树的 最大子树节点数   叶节点的n - sum 是最大的, 不可能成为树的重心
    ans = min(ans, res); //遍历过的假设重心中,最小的最大连通块的数量

    return sum;
}
//dfs之所以return的值是sum 不是因为答案输出需要,而是for循环内每次函数递归时(dfs(j)),需要返回sum来完成后面更新节点子连通块数量的最大值操作res = max(res, s)

int main()
{
    scanf("%d", &n);

    memset(h, -1, sizeof h); //初始化h数组 先将其全部指向空节点

    for (int i = 0; i < n - 1; i ++ ) //树中是不存在环的,对于有n个节点的树,必定是n-1条边, 即每个数最多连接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;
}
posted @ 2024-04-16 17:43  MsEEi  阅读(19)  评论(0)    收藏  举报