1. 程式人生 > 其它 >CF #781 Div. 2 C. Tree Infection [貪心]

CF #781 Div. 2 C. Tree Infection [貪心]

題面翻譯:

給出一個有 n 個頂點的有根樹,節點 1 為根節點。一開始,所有節點都是 健康的。
每秒將進行 兩次操作,先開始傳染 spreading,然後進行注射 injection
  1. spreading: 對於每一個節點 v,如果它有一個已感染的子女,你可以選擇對 v 的其他子女進行感染
  2. injecting:選擇任意一個健康節點進行感染

這個過程每秒都重複執行直到整棵樹被感染。你需要找出感染整棵樹需要的最小秒數。

原題連結 [codeforces.com]

思路:

貪心、堆

首先可以發現,一個節點的子女 和 另一個節點的子女 是獨立的。那我們可以按照父節點進行分組,就是所有兄弟節點在同一個組內,我們記組數為

每個組的節點數量為ci

因為是獨立的,那麼說明至少需要進行 m 次注射操作。每秒只能注射一次,所以同時 m 秒也是下限。

然後我們考慮怎麼節省時間。第一秒過後,我們注射一個節點之前,可以直接感染前面注射點的健康兄弟節點。然後等到所有分組都被有節點被注射後(第 m 秒結束)。我們就看看有沒有哪個組還有很多健康節點,有就去進行注射操作節省時間。

那說明我們要儘量減少 m 秒之後花費的時間,即,儘量在第 m 秒結束之前感染儘可能多的節點。

可以發現,在第 j 秒注射的組,在到第 m 秒結束時就能總共感染 min(m - j + 1, ci的節點。可以看出如果一個組已經全感染了,那他就對節省時間沒有貢獻了。那麼進行貪心,把組按照節點個數從大到小排序,儘量讓節點數多的減掉更多,貢獻就是最大的。

考慮 m 秒以後。此時存活的組每秒都會減少一個健康節點,注射可以給特定組減多一個健康節點。直到健康節點最多的那個組也少於 3 個,那麼下一秒它也全部感染了。題目整顆樹節點最多2×105過程是 O(n * log2n) 的,可以直接模擬。

實現:

我們用陣列去記錄每個節點的子女數量,就完成了分組。m 秒之後的部分,全部減一不會影響到堆的結構,直接在外面記錄減了多少就行。注射操作用一個數組記錄,下標 i 的位置就表示第 i 組注射了多少,進堆的時候排序關鍵字就是 (原數量(m 秒之後的) - 注射次數)。

程式碼實現
#include <bits/stdc++.h>

using namespace std;
using ll = long long;
const int MAX_N = 2e5;

int chd[MAX_N + 1];
vector<int> grp;
int t[MAX_N + 1];

struct cmp
{
    bool operator()(int a, int b) const
    {
        return grp[a] + t[a] < grp[b] + t[b];
    }
};

priority_queue<int, vector<int>, cmp> q;

void solve()
{
    memset(chd, 0, sizeof(chd));
    memset(t, 0, sizeof(t));
    while (q.size()) q.pop();
    grp.clear();
    int n;
    scanf("%d", &n);

    chd[0] = 1;
    for (int i = 2; i <= n; ++i)
    {
        int pi;
        scanf("%d", &pi);
        ++chd[pi];
    }

    
    for (int i = 0; i <= n; ++i)
    {
        if (chd[i]) grp.push_back(chd[i]);
    }

    sort(grp.begin(), grp.end(), greater<int>());
    n = grp.size();

    for (int i = 0; i != n; ++i)
    {
        // after all groups injected;
        grp[i] -= n - i;
        if (grp[i] > 0) q.emplace(i);
    }

    int exa = 0;
    while (q.size())
    {
        int i = q.top();
        q.pop();
        ++exa;
        int will = grp[i] + t[i] - exa;
        if (will <= 0)
        {
            if (will < 0) --exa;
            break;
        }
        --t[i]; //inj
        if (will >= 1) q.push(i);
    }

    printf("%d\n", exa + n);
}

int main(void)
{
    int cases;
    scanf("%d", &cases);
    while (cases--) solve();
    return 0;
}