CF685B 树的重心

1 CF685B 树的重心

2 题目描述

时间限制 \(3s\) | 空间限制 \(256M\)

自从那块恶魔般的镜子击中 \(Kay\) 的眼睛后,他对玫瑰的美丽再也没有兴趣了。现在他喜欢看雪花。很久以前,他发现了一个巨大的雪花,它是由 \(n\) 个节点组成的树(连接的无环图)。根结点的编号为 \(1\)\(Kay\) 对这棵树的结构很感兴趣。在做了一些研究之后,他形成了他感兴趣的 \(q\) 个查询。第 \(i\) 次查询找到节点 \(v_i\) 的子树的重心。一棵树或者子树的重心是这棵树或者子树的一个节点,该节点满足:如果我们从树或者子树上删除这个节点,这个树或者子树的最大的连通分量中节点个数不大于整棵树或者子树的节点个数的一半。

3 题解

前置知识:如果以一个节点为根,其所有真子树中拥有最多节点数的最大真子树的节点个数小于等于总节点数的一半,那么该节点就是数的重心。

很明显的是,这道题中我们需要先求出以每个节点为根节点的子树的节点数。这是因为在计算树的重心的过程中,我们需要用到节点数这一重要数据来确定一个节点是否是一棵树的重心。同时,我们还需要一个节点的最大真子树的根节点这一坐标,方便我们判断该节点的最大真子树的节点个数是否小于等于总节点个数的一半。

我们考虑暴力算法:对于每一个节点,遍历其所有子节点。对于每个子节点,其最大真子树的节点数其实不只是我们在上面简简单单的求最大真子树的节点数。因为这些子树都是该节点的儿子节点,而该节点应该还有一棵由其父节点组成的子树。我们该如何计算这颗子树的节点数呢?我们发现:这棵子树的节点数正好为整棵树的节点数减去当前节点的节点数。我们每次计算时将该节点已经算出的最大真子树的节点数和整棵树的节点数减去当前节点的节点数这两个值取最大值,再判断该最大值是否小于等于整棵树的总节点数的一半,进而判断该点是否是树的重心。

我们观察发现,数据范围中的 \(n \le 3*10^5\),明显无法使用上述 \(O(n^2)\) 的暴力算法。我们想到,第一层枚举所有节点的循环必不可少,所以我们只能考虑优化第二层枚举节点子树内的所有节点的循环。这里,我们有一个极其厉害的性质:一棵树的重心肯定在其根节点与其最大真子树的重心之间的简单路径上。

我们简单证明一下以上性质:如果我们的重心不在其最大真子树或者根节点上,那么该节点一定不是树的重心。首先,当该节点不在整棵树的根节点或者最大真子树上时,它肯定存在于某一棵子树中。这时,从该节点到根节点的路径上的节点再加上除了该节点所在子树外的所有子树的节点数之和一定大于总节点数的一半,该节点也就不是树的重心了。而当一个节点在最大真子树上且不在最大真子树的重心与根节点的路径上时,最大真子树中肯定存在一棵子树,使得该子树是我们选取的节点的子树,且该子树包含最大真子树的重心,并且可以加上最大真子树与根节点之间的节点。容易得知,这种情况没有在最大真子树的重心和根节点之间选取一个节点更优,进而无法成为整棵树的重心。

证明了以上性质后,我们就可以从下向上枚举该路径上的所有节点并判断了。这里注意:假如存在一个节点,是深度最大的满足总节点数减去该节点的节点数小于等于总节点数的一半的节点,那么它一定是该树的重心,这是因为深度越小,总节点数减去该节点的节点数得到的值越小,同时该节点的最大真子树的节点数也越大。而当总节点数减去该节点的节点数最大且满足条件时,总节点数减去该节点的节点数与该节点的最大真子树的节点数最接近,该节点也就是最有可能成为重心的节点。由于一棵树一定拥有至少一个重心,该节点就是树的重心。

注意:如果一开始就判定出根节点的最大真子树的节点数小于等于总节点数的一半,那么该树的重心就是该树的根节点。

这里由于我们每次枚举到的要计算重心的节点的最大真子树的重心一定是由某个重心向上枚举得到的,我们直接接上上一次的枚举即可,最差情况下时间复杂度是节点数,也就是 \(O(n)\),可以跑过 \(3*10^5\)

4 代码(空格警告):

#include <iostream>
using namespace std;
const int N = 3e5+10;
int n, q, tot;
int p[N], head[N*2], last[N*2], ver[N*2];
int siz[N], bson[N], ans[N], v[N];
void add(int x, int y)
{
    ver[++tot] = y;
    last[tot] = head[x];
    head[x] = tot;
}
void dfs(int x, int fa)
{
    siz[x] = 1;
    ans[x] = x;
    bson[x] = 0;
    for (int i = head[x]; i; i = last[i])
    {
        int y = ver[i];
        if (y == fa) continue;
        dfs(y, x);
        siz[x] += siz[y];
        if (siz[bson[x]] < siz[y]) bson[x] = y;
    }
    if (siz[bson[x]] * 2 > siz[x])
    {
        int f = ans[bson[x]];
        while ((siz[x] - siz[f]) * 2 > siz[x]) f = p[f];
        ans[x] = f;
    }
}
int main()
{
    cin >> n >> q;
    for (int i = 2; i <= n; i++)
    {
        cin >> p[i];
        add(i, p[i]);
        add(p[i], i);
    }
    dfs(1, -1);
    for (int i = 1; i <= q; i++) cin >> v[i];
    for (int i = 1; i <= q; i++) cout << ans[v[i]] << endl;
    return 0;
}

欢迎关注我的公众号:智子笔记

posted @ 2021-02-19 09:19  David24  阅读(125)  评论(0编辑  收藏  举报