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

题意:

一共 n n 个人,每个人有两个属性, m i m_i p i p_i ,表示收买此人的两种方式,一种方式是花 p i p_i 的钱,另一种方式是已经收买了 m i m_i 个人,现询问收买所有人的最小花费。 ( 1 n 2 1 0 5 , 1 p i 1 0 9 , 0 m i n 1 ) (1\leq n\leq 2*10^5,1\leq p_i\leq 10^9,0\leq m_i\leq n-1)


思路:

比赛时看到了此题,第一思路是贪心。当时的贪心策略是先把一定要付钱的人都找出来,即对于第 i i 层来说, n s z [ i ] < s z [ i ] n-sz[i]<sz[i] ,即第 i i 层的人需要用钱收买,边收买边判断 n s z [ i ] + c n t < s z [ i ] c n t n-sz[i]+cnt<sz[i]-cnt 是否成立,成立则继续收买。然后再将第 0 0 层的人直接算到所有人集合中,接下来每次选取花费最少的人收买,并且收买完后看是否有一层的人可以被集体收买。

这样贪心不对。主要原因在于每次选取花费最少的人不一定最优,因为可能选取花费次小的但是必定需要收买的人,然后花费最少的人所在的层就直接被集体收买了。因此最大的问题就在于没有处理好在贪心的过程中,有一些人变成了必须花钱才能收买,而这种策略没有收买。

因此我们需要换一种策略,每次选人将所有必须花钱的人找出来,并且在这些人中选取花费最小的。

我们可以将所有人按 m m 分层,再倒着枚举层数,枚举到第 i i 层时,就将第 i i 层的人加入小根堆中,将所有必须付钱的人从堆中弹出。

于是问题就变成了枚举到第 i i 层时,堆中最多有多少人。不难发现答案为 n i n-i 人,因为想要集体收买该层需要 i i 人,所以队列中最多有 n i n-i 人,至此即可完成此题。


总结:

贪心问题,最忌讳的就是有一个不成熟的想法就马上去实现,然后实现了半天之后还 w a wa 了… 这会对比赛结果产生很大的负面影响。

因此贪心问题,考虑到一个策略之后,一定要反复 h a c k hack 自己,并且要尝试去证明该策略的正确。这一过程其实花不了太多的时间,最多 10 m i n 10min 左右就可以发现问题,而且有利于进一步完善思路。因此想要策略之后一定要再多想想,切忌盲目切题!


代码:

#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;
}
发布了244 篇原创文章 · 获赞 115 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/qq_41552508/article/details/102766014