CF436E Cardboard Box(贪心)

题意

\(n\) 个关卡,对每个关卡,你可以花 \(a_i\) 代价得到一颗星,也可以花 \(b_i\)
代价得到两颗星,也可以不玩。问获得 \(w\) 颗星最少需要多少时间。

数据范围

\(1 \leq n \leq 300000,1 \leq w \leq 2n\)

思路

对于本题有一个很直接的想法,把花费 \(b_i\) 看成是花费 \(a_i\) 得到一颗星以后再花费 \(b_i-a_i\) 的代价再得到一颗星。

如果直接用小根堆 \(q1\) 维护最小代价,每次选第一颗星后把第二颗星插入堆中,得到的答案不一定是最优的。因为假设现在还需 \(2\) 颗星,贪心的选了 \(i\) 的第二颗星,再选 \(j\) 大一点的第一颗星,可能并不比直接选 \(j\) 的二颗星的代价小(也就是没有考虑选第一颗星后再插入的第二颗星的代价)。

可以再开一个小根堆 \(q2\) 来维护同时取两颗星的代价。每次取出时判断一下是零散地选两颗星代价小还是一次性取两颗星代价小。但是需要注意,如果一次性选两颗星代价更小,不能直接把两颗都取走,这是因为取了第一颗星后,并不保证取第二颗星和另一颗星的代价就比 \(q2\) 此时的最小值更优(如 \(2,2,6,10,8,11\) 这组数据,如果直接取完,得到的答案是 \(18\),但最优解是 \(17\))。只能说这种选法比零散的选法一定更优或者等价,但取出一个后就不一定是最优的了(如果取第二颗星的代价实在太小了,那么也会被再次取出)。

同时由于 \(q1\)\(q2\) 中维护的是相同元素,于是需要用一个数组记录该星是否被选过(也方便最后输出答案),如果当前堆顶的元素已经被使用过了就弹出,直到没有被使用过。

code:

#include<queue>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=6e5+10;
#define LL long long
int n,m,a[N];LL ans;bool vis[N];
struct node{int id;LL val;bool operator <(const node &t)const{return val>t.val;};};
priority_queue<node> q1,q2;
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) scanf("%d%d",&a[i],&a[i+n]),a[i+n]-=a[i],q1.push(node{i,a[i]}),q2.push(node{i,a[i+n]+a[i]});
	while(m--)
	{
		while(q1.size()&&vis[q1.top().id]) q1.pop();
		while(q2.size()&&vis[q2.top().id]) q2.pop();
		int now=q1.top().id;q1.pop();
		while(q1.size()&&vis[q1.top().id]) q1.pop();
		if(m&&q2.size()&&a[now]+q1.top().val>=q2.top().val){q1.push(node{now,a[now]});now=q2.top().id;q2.pop();}
		if(now<=n) q1.push(node{now+n,a[now+n]});ans+=a[now];vis[now]=true;
	}
	printf("%lld\n",ans);
	for(int i=1;i<=n;i++) printf("%d",vis[i]+vis[n+i]);puts("");
	return 0;
}