Loliko_LinawZ

导航

 

每日一道算法日常预防老年痴呆。

 

先上题目

给定一颗树,树中包含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

 

根据题目把示例图画了出来:

 

这里是拿点1和点4做树的重心来做示范,题目的要求是在连通块最多的节点数当中求出最小的数。

解题思路:模拟一个队列存储节点,入队头节点(假设这就是树的重心)以后,开始搜索该节点的所有连通块,然后返回所有连通块当中最大的节点数。最后求所有节点的最大节点数当中的最小数值。本题的难点在于,如果头结点不是根节点了,(比如说现在要搜索节点4的连通块了)我们要如何向上搜索与节点1相连的那个连通块有多少个节点数?

解决方法是其实可以直接把节点4看成一个根节点,与之相连的节点1所在的连通块的节点数,其实就是 所有的节点数 - 节点4这一连通块的总数。

详见代码注释

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;

const int N = 1e5+10;

int h[N], e[2*N], ne[2*N];  //因为是无向图 考虑有两条边 所以开N*2
int idx = 0;    
int n, ans = N;
bool st[N];     //判断当前节点有没有搜索过

void add(int a, int b)  //模拟一个队列
{
    e[idx] = b;
    ne[idx] = h[a];
    h[a] = idx++;
}

int dfs(int u)
{
    int size = 0;   //存储当前连通块节点数
    st[u] = true;   //头结点标记为已遍历
    int sum = 0;    //选中点的子树的节点数
    for(int i = h[u]; i != -1; i = ne[i])   //从给定的节点内开始深搜
    {
        int j = e[i];
        if(!st[j])      //如果深搜到一个没有走过的节点
        {
            int s = dfs(j);     //存储当前节点子树的所有节点数
            size = max(size, s);    //取子树节点数中的最大值
            sum += s;       //遍历完一个连通块 就把子树的节点数累加
        }
    }
    size = max(size, n - sum - 1);  //如果不是头结点,还需要比较子树节点和连接头结点的连通块哪个数值更大
                                    //n - sum - 1是去掉当前节点后,父节点所在连通块的大小
    
    ans = min(ans, size);   
    
    return sum + 1;     //返回sum+1是因为 sum是选中点的子树的节点数,这里需要把选中的点也算进去 所以+1
}


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);
    }
    
    dfs(1);
    cout<<ans<<endl;
    return 0;
}
View Code

 

posted on 2020-09-11 11:52  数码暴龙猪  阅读(160)  评论(0)    收藏  举报