【題解】 CF1251E2 Voting (Hard Version) 帶悔貪心
阿新 • • 發佈:2020-09-20
Legend
Link \(\textrm{to Codeforces}\)。
現在有 \(n\ (1 \le n \le 2 \cdot 10^5)\) 個人參與投票,你想讓他們都投給你,他們每個人有兩個引數 \(m_i,p_i\)。
第 \(i\) 個人為你投票有如下兩種情況:
- 你主動支付 ta \(p_i\ (1 \le p_i \le 10^9)\) 元;
- 存在 \(m_i\ (0 \le m_i < n)\) 個其他的人已經投了你。
請最小化所有人都投票給你的花費。
Editorial
考慮花費從小到大貪心,記錄當前有多少個人 \(vote\) 已經為你投票(包括給錢的和跟風的),維護一個集合 \(S\)
你主動支付給了 ta 錢,但當初如果你不給 ta 錢,而是給另一個人錢,那麼 ta 現在會跟風。
看看維護這個集合有什麼用吧:設我現在貪心到的這個人為 \(y\),我們找到 \(S\) 中花費最大的那位朋友 \(x\)。
如果在當初選 \(x\) 的時候我選了 \(y\),那麼現在 \(x\) 就會跟風,於是就省下了賄賂 \(x\) 的錢,所以不如直接不選 \(x\)。
於是我們不斷的貪心並且反悔,用 \(\rm{multiset}\) 維護可反悔集合即可。
複雜度 \(O(n \log n)\)。
Code
回首一年前的自己,對於此題毫無思路,現在一眼切掉了。雖然我並不強,但人啊,總要進步的,不是嗎?
#include <bits/stdc++.h> using namespace std; const int MX = 2e5 + 23; #define LL long long int read(){ char k = getchar(); int x = 0; while(k < '0' || k > '9') k = getchar(); while(k >= '0' && k <= '9') x = x * 10 + k - '0' ,k = getchar(); return x; } struct People{ int p ,m ,id; bool operator <(const People &B)const{ return p == B.p ? m < B.m : p < B.p; } }Pp[MX] ,Pm[MX]; int doit[MX]; bool cmpp(People A ,People B){return A < B;} bool cmpm(People A ,People B){return A.m < B.m;} void solve(){ int n = read(); int vote = 0 ,ok = 1 ,r = 1; LL cost = 0; for(int i = 1 ,p ,m ; i <= n ; ++i){ m = read() ,p = read(); Pp[i] = Pm[i] = (People){p ,m ,i}; doit[i] = 0; } sort(Pp + 1 ,Pp + 1 + n ,cmpp); sort(Pm + 1 ,Pm + 1 + n ,cmpm); while(ok <= n && Pm[ok].m <= vote){ if(!doit[Pm[ok].id]){ doit[Pm[ok].id] = 1; ++vote; } ++ok; } multiset<People> change; while(vote != n){ while(doit[Pp[r].id]) ++r; cost += Pp[r].p; ++vote; doit[Pp[r].id] = 2; if(!change.empty()){ cost -= change.rbegin()->p; auto kksk = change.end(); --kksk; change.erase(kksk); } while(ok <= n && Pm[ok].m <= vote){ if(!doit[Pm[ok].id]){ doit[Pm[ok].id] = 1; ++vote; } if(doit[Pm[ok].id] == 2){ change.insert(Pm[ok]); } ++ok; } } cout << cost << endl; } int main(){ int t; cin >> t; while(t--) solve(); return 0; }