题目大意
你到一家正在进行特价活动的馅饼店买馅饼。规则是每全价购买一个馅饼,都可以免费得到一个价格严格更低的馅饼。
求出为所有馅饼支付的最小花费。
(Nle 5e5,A_ile 1e9)
做法
自己能想到的最优做法就是(O(n^2))
考虑dp 一定从大到小 每次选择一段 如果左半部分可以覆盖右半部分就更新 没有单调性也没有优化空间
题解的做法是可反悔贪心 转化问题为免费拿到的和尽可能大
也是从大到小排序 接着考虑怎么维护这个反悔过程 考虑维护免费拿过的集合的决策贡献
一样的一起处理 比如有(cnt)个(a) 前面有(s)个馅饼 (c)个免费拿的 还剩(s-2*c)个和当前配对的
讨论(s-2*cge 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
",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
",f[n]);
return 0;
}*/