Tree Infection(思维,二分)

140 阅读2分钟

Problem - 1665C - Codeforces

原题题面

image.png

题目描述

给出一棵无环树,每个树节点有两个状态:健康,发病。

接下来每一秒有一个操作,分为两步:

  • 传播:对于同父节点的兄弟,若兄弟中有至少一个发病的节点,则可以选择一个健康的节点使其感染发病。

  • 感染:选择任意一个未发病的节点,使其发病。

询问最少多少次操作后,所有节点均发病。

数据格式

image.png

输入样例

5
7
1 1 1 2 2 4
5
5 5 1 4
2
1
3
3 1
6
1 1 1 1 1

输出样例

4
4
2
3
4

题目分析

这是一道 思维 题。

首先最开始的状态是所有节点均健康,我们按节点的兄弟关系将其分为多个群组,可以知道对于每个群组而言,一定要有最开始发病的人,即每个群组至少占用一次操作。

我们设群组的数量为 nn,按群组中兄弟个数从小到大排序。可以以贪心的思想得出需要按兄弟个数从多到少进行每个群组的第一次感染操作。

在每个群组都有感染的节点后,可以接下来每次操作无论作用于哪个节点,每个含有健康节点的群组都会至少减少一名健康节点。于是,我们可以二分剩下的操作数,先对所有群组进行操作的第一步,若其还有健康节点,再进行操作的第二步,同时统计第二步需要的次数,与二分的操作数进行比较,并继续判断。

Accept代码

#include <bits/stdc++.h>

using namespace std;

const int N = 200010;
int g[N];
int n;
vector<int> f;

bool check(int x)
{
    int k = 0;
    for (int i = f.size() - 1; ~i; i --)
        if (f[i] > x) k += f[i] - x;
        else break;
    return x >= k;
}

void bfs()
{
    vector<int> v;
    v.push_back(1);
    for (int i = 1; i <= n; i ++) 
        if (g[i] > 0) v.push_back(g[i]);
    sort(v.begin(), v.end());

    int m = v.size();
    f.clear();
    for (int i = m - 1; ~i; i --)
    {
        int x = v[i] + m - 1 - i;
        if (x - m > 0) f.push_back(x - m);
    }
    
    sort(f.begin(), f.end());
    int l = 0, r = 2e5;
    while (l < r)
    {
        int mid = l + r >> 1;
        if (check(mid)) r = mid;
        else l = mid + 1;
    }
    cout << m + r << "\n";
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);

    int t; cin >> t;
    while (t --)
    {
        cin >> n;
        for (int i = 1; i <= n; i ++) g[i] = 0;
        for (int i = 2; i <= n; i ++)
        {
            int fa; cin >> fa;
            g[fa] ++;
        }
        bfs();
    }
    return 0;
}