CF335F Buy One, Get One Free
题目大意
你到一家正在进行特价活动的馅饼店买馅饼。规则是每全价购买一个馅饼,都可以免费得到一个价格严格更低的馅饼。
求出为所有馅饼支付的最小花费。
\(N\le 5e5,A_i\le 1e9\)
做法
自己能想到的最优做法就是\(O(n^2)\)
考虑dp 一定从大到小 每次选择一段 如果左半部分可以覆盖右半部分就更新 没有单调性也没有优化空间
题解的做法是可反悔贪心 转化问题为免费拿到的和尽可能大
也是从大到小排序 接着考虑怎么维护这个反悔过程 考虑维护免费拿过的集合的决策贡献
一样的一起处理 比如有\(cnt\)个\(a\) 前面有\(s\)个馅饼 \(c\)个免费拿的 还剩\(s-2*c\)个和当前配对的
讨论\(s-2*c\ge cnt\)于是直接配对扔进集合即可
否则有剩下的\(res\)个 考虑对于这几个的决策
看我们之前拿过的集合里最小的元素\(b\)(此时\(b\)有可能小于\(a\)因为往下看我们有对于决策的修改)
\(b<a\) 那一定是把\(a\)换过去 同时释放了\(b\)又多了一个位置可以再匹配一个(当然如果只剩一个\(a\)的话就没有办法扔进去了)
否则 我们考虑有以下若干种决策
1.把\(b\)取出来 放一个\(a\)进去 释放了\(b\)再匹配一个\(a\)(与上面那种决策相同)
2.保留\(b\) 买\(a\)
抽象这个过程变为保留\(b\)再加入一个\(2a-b\)(当然需要保证至少还存在两个\(a\) 不过还需要保证\(2a-b>0\)因为这种决策绝对不优)
这个抽象过程的正确性在于我们考虑以下过程
1.最后单选了\(2a-b\)就是全价买了\(b\) 把两个名额留给了\(a\)
2.最后单选了\(b\)相当于全价买了两个\(a\) 提供了两个名额
3.同时选择了两个\(2a-b\)和\(b\)也就是相当于既保留了\(b\)同时又用了两个名额给\(a\)
总之就是很高级...
代码如下:(注释掉的部分是n2)
//Love and Freedom.
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#define ll long long
#define inf 20021225
#define N 500010
using namespace std;
int read()
{
int s=0,f=1; char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-') f=-1; ch=getchar();}
while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
return s*f;
}
int n,a[N]; ll v[N]; int tot; priority_queue<ll,vector<ll>,greater<ll> > q;
int main()
{
n=read(); ll ans=0;
for(int i=1;i<=n;i++) ans+=(a[i]=read()); sort(a+1,a+n+1); reverse(a+1,a+n+1);
for(int i=1,j;i<=n;i=j)
{
for(j=i;j<=n&&a[i]==a[j];j++); int cnt=j-i;
int cur=min(cnt,i-1-(int)q.size()*2),rst=min(i-1,cnt)-cur; tot=0;
for(int x=1;x<=cur;x++) v[++tot]=a[i];
for(int x=1;x<=rst;x+=2)
{
ll val=q.top(); q.pop();
if(val<a[i])
{
v[++tot]=a[i];
if(x<rst) v[++tot]=a[i];
}
else
{
v[++tot]=val;
if(x<rst) v[++tot]=2*a[i]-val;
}
}
for(int x=1;x<=tot;x++) if(v[x]>=0) q.push(v[x]);
}
while(!q.empty()) ans-=q.top(),q.pop();
printf("%lld\n",ans);
return 0;
}
/**
ll f[N],a[N],pre[N]; int n,l[N],r[N];
bool check(int x,int y,int t){int L=max(x,l[y]),R=min(t,r[y]); L-=x,R-=y; return L>R;}
int main()
{
n=read();
for(int i=1;i<=n;i++) a[i]=read(); sort(a+1,a+n+1); reverse(a+1,a+n+1);
for(int i=1;i<=n;i++) pre[i]=pre[i-1]+a[i],l[i]=a[i]==a[i-1]?l[i-1]:i;
for(int i=n;i;i--) r[i]=a[i]==a[i+1]?r[i+1]:i;
for(int i=1;i<=n;i++)
{
f[i]=f[i-1]+a[i]; int p=i-1;
for(int j=i-2;~j;j--)
{
int mid=i+j+1>>1;
if(check(j+1,mid+1,i)) if(f[j]+pre[mid]-pre[j]<f[i]) f[i]=f[j]+pre[mid]-pre[j],p=j;
}
printf("%d ",p);
}
printf("%lld\n",f[n]);
return 0;
}*/