【CF Contest-1251 E2】Voting (Hard Version)【貪心】

題意:

一共 nn 個人,每個人有兩個屬性,mim_ipip_i,表示收買此人的兩種方式,一種方式是花 pip_i 的錢,另一種方式是已經收買了 mim_i 個人,現詢問收買所有人的最小花費。(1n2105,1pi109,0min1)(1\leq n\leq 2*10^5,1\leq p_i\leq 10^9,0\leq m_i\leq n-1)


思路:

比賽時看到了此題,第一思路是貪心。當時的貪心策略是先把一定要付錢的人都找出來,即對於第 ii 層來說,nsz[i]<sz[i]n-sz[i]<sz[i],即第 ii 層的人需要用錢收買,邊收買邊判斷 nsz[i]+cnt<sz[i]cntn-sz[i]+cnt<sz[i]-cnt 是否成立,成立則繼續收買。然後再將第 00 層的人直接算到所有人集合中,接下來每次選取花費最少的人收買,並且收買完後看是否有一層的人可以被集體收買。

這樣貪心不對。主要原因在於每次選取花費最少的人不一定最優,因爲可能選取花費次小的但是必定需要收買的人,然後花費最少的人所在的層就直接被集體收買了。因此最大的問題就在於沒有處理好在貪心的過程中,有一些人變成了必須花錢才能收買,而這種策略沒有收買。

因此我們需要換一種策略,每次選人將所有必須花錢的人找出來,並且在這些人中選取花費最小的。

我們可以將所有人按 mm 分層,再倒着枚舉層數,枚舉到第 ii 層時,就將第 ii 層的人加入小根堆中,將所有必須付錢的人從堆中彈出。

於是問題就變成了枚舉到第 ii 層時,堆中最多有多少人。不難發現答案爲 nin-i 人,因爲想要集體收買該層需要 ii 人,所以隊列中最多有 nin-i 人,至此即可完成此題。


總結:

貪心問題,最忌諱的就是有一個不成熟的想法就馬上去實現,然後實現了半天之後還 wawa 了… 這會對比賽結果產生很大的負面影響。

因此貪心問題,考慮到一個策略之後,一定要反覆 hackhack 自己,並且要嘗試去證明該策略的正確。這一過程其實花不了太多的時間,最多 10min10min 左右就可以發現問題,而且有利於進一步完善思路。因此想要策略之後一定要再多想想,切忌盲目切題!


代碼:

#include <bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof a);
#define rep(i,a,b) for(int i = a; i <= b; i++)
#define per(i,a,b) for(int i = a; i >= b; i--)
#define __ ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
typedef long long ll;
typedef double db;
const int N = 2e5+100;
const db EPS = 1e-9;
using namespace std;

void dbg() {cout << "\n";}
template<typename T, typename... A> void dbg(T a, A... x) {cout << a << ' '; dbg(x...);}
#define logs(x...) {cout << #x << " -> "; dbg(x);}

int n,m,p;
vector<int> v[N];
priority_queue<int> q;

int main()
{
	int _; scanf("%d",&_);
	while(_--){
		scanf("%d",&n);
		rep(i,0,n) v[i].clear();
		rep(i,1,n) scanf("%d%d",&m,&p), v[m].push_back(p);
		while(q.size()) q.pop();
		ll ans = 0;
		per(i,n,0){
			for(auto& tp:v[i]) q.push(-tp);
			while(q.size() > n-i) ans -= q.top(), q.pop();
		}
		printf("%lld\n",ans);
	}
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章