【CF335F】Buy One, Get One Free(带悔贪心)
- 有 \(n\) 个物品,你每购买一个物品可以免费获得一个价格严格小于它的物品,求得到所有物品的最小代价。
- \(1\le n\le5\times10^5\)
带悔贪心
假如并非“严格小于”,而是“小于等于”,这就是 AGC001A。
但我们依旧可以考虑排序后从大到小决策。不过由于现在的策略比较复杂,需要带悔贪心。
首先,如果仍然存在尚未配对的物品,直接拼上去一定不劣。
否则,假设当前有两个价值为 \(x\) 的物品,我们有以下策略:
- 直接购买这两个价值为 \(x\) 的物品,代价为 \(2x\)。
- 原来有一组价值为 \((a,b)\) 的物品,则我们可以考虑将 \((a,b)\) 拆开得到两组物品 \((a,x)\) 和 \((b,x)\),代价为 \(b\)。
如果 \(2x\le b\),肯定选择第一种策略。
如果 \(2x > b\),我们暂时先选择第二种策略,并考虑如何实现反悔,即如何将它转换成第一种策略。
发现从 \((a,x),(b,x)\) 到 \((a,b),x,x\) 代价为 \(2x-b\),所以只需要往堆里扔入一个 \(2x-b\) 的元素就可以表示这里的策略转换了。(而且,注意到这里策略转换和原本的选择第二种策略效果都是新产生两个尚未配对的物品,可以视作等价,因此不必特意区分。)
策略转换之后 \((a,b)\) 又变得可以拆开了,所以又要新建一个代价为 \(b\) 的物品,注意到 \(2x-b < b\)(因为 \(x < b\)),必然会先选 \(2x-b\) 再选 \(b\),可以一起扔到堆里。
但还要注意一个问题,就是有了表示策略转换的元素后,便可能出现堆中元素 \(v\) 比当前元素 \(x\) 小的情况。此时我们必然选择将 \(v\) 执行掉,但如果再想要执行策略转换代价为 \(2x-v > x\),所以不可能再反悔,只需往堆里扔入两个 \(x\) 即可(也可能只剩下一个 \(x\),则扔入一个 \(x\) 并标记增加一个新的尚未配对物品)。
代码:\(O(n\log n)\)
#include<bits/stdc++.h>
#define Cn const
#define CI Cn int&
#define N 200000
using namespace std;
namespace FastIO
{
#define FS 100000
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define tc() (FA==FB&&(FB=(FA=FI)+fread(FI,1,FS,stdin),FA==FB)?EOF:*FA++)
char oc,FI[FS],*FA=FI,*FB=FI;
Tp void read(Ty& x) {x=0;while(!isdigit(oc=tc()));while(x=(x<<3)+(x<<1)+(oc&15),isdigit(oc=tc()));}
Ts void read(Ty& x,Ar&... y) {read(x),read(y...);}
}using namespace FastIO;
int n,a[N+5],c[N+5],q[N+5],dc,dv[N+5];priority_queue<int,vector<int>,greater<int> > Q;
int main()
{
int i;for(read(n),i=1;i<=n;++i) read(a[i]);sort(a+1,a+n+1);//排序
for(i=1;i<=n;++i) dv[i]=a[i];dc=unique(dv+1,dv+n+1)-dv-1;
int j=1;for(i=1;i<=n;++i) {while(dv[j]<a[i]) ++j;++c[j];}
int t,w=0,ct=0;long long ans=0;for(i=dc;i;--i)//从大到小选择
{
t=0;while(c[i]&&w) --w,q[++ct]=dv[i],--c[i];//还存在尚未配对的物品
while(c[i]&&!Q.empty()&&Q.top()<dv[i]) ans+=Q.top(),Q.pop(),q[++ct]=dv[i],--c[i],c[i]?(q[++ct]=dv[i],--c[i]):++w;//堆顶元素小于当前元素
while(c[i]>=2&&!Q.empty()&&Q.top()<2*dv[i]) ans+=Q.top(),q[++ct]=2*dv[i]-Q.top(),q[++ct]=Q.top(),Q.pop(),c[i]-=2;//注意带悔
while(ct) Q.push(q[ct--]);while(c[i]--) ans+=dv[i],++w;//把产生的元素加入堆里;剩余的物品尚未配对
}return printf("%lld\n",ans),0;
}
待到再迷茫时回头望,所有脚印会发出光芒