CF #781 Div. 2 C. Tree Infection [貪心]
阿新 • • 發佈:2022-04-09
題面翻譯:
給出一個有 n 個頂點的有根樹,節點 1 為根節點。一開始,所有節點都是 健康的。每秒將進行 兩次操作,先開始傳染 spreading,然後進行注射 injection
- spreading: 對於每一個節點 v,如果它有一個已感染的子女,你可以選擇對 v 的其他子女進行感染
- injecting:選擇任意一個健康節點進行感染
這個過程每秒都重複執行直到整棵樹被感染。你需要找出感染整棵樹需要的最小秒數。
思路:
貪心、堆
首先可以發現,一個節點的子女 和 另一個節點的子女 是獨立的。那我們可以按照父節點進行分組,就是所有兄弟節點在同一個組內,我們記組數為 m 每個組的節點數量為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; }