CF335F 题解
\(CF335F\) \(Buy\) \(One\),\(Get\) \(One\) \(Free\)
题意:一家馅饼店买馅饼。规则是每全价购买一个馅饼,都可以免费得到一个价格严格更低的馅饼。求出为 \(n\) 个馅饼支付的最小花费。
\(n \leq 5\times 10^5\),\(1\leq a_i \leq 10^9\)。
\(sol\):考虑可撤销贪心+小根堆维护。
首先价格相同的馅饼可以放到一起考虑,从大到小排序后考虑每种不同价格的馅饼。则第 \(i\) 种最多白嫖的个数为 \(p=\min(c_i,num-2*sum)\) ,其中 \(c_i\) 为馅饼个数,\(num\) 为已经考虑的更贵的馅饼的总数,\(sum\) 为前面决定白嫖的馅饼数量。这样不一定最优,但不慌,我们还可以反悔。我们把要白嫖的馅饼的贡献(省的钱)放入一个小根堆里。现在对于第 \(i\) 种我们要处理剩下 \(\min(c_i,num)-p\) 的白嫖机会。
每次我们取出堆顶元素 \(k\)
-
如果 \(k\ge val_i\) 说明仍然白嫖 \(k\) 更优。但是如果这时第 \(i\) 种馅饼还剩至少 \(2\) 个,并且\(2\times val_i-k>=0\) 说明我们还可以选择不买 \(k\) 这个馅饼,而用他白嫖一个当前馅饼,而原来用来白嫖 \(k\) 的馅饼也能提供一次白嫖的机会,而要撤销白嫖 \(k\) 的操作可以向堆中加入一个权值为 \(2val_i-k\) 的元素。
-
如果 \(k<val_i\) ,则白嫖当前馅饼更优,如果如果这时第 \(i\) 种馅饼还剩至少 \(2\) 个,则我们还可以再白嫖一个,这里需要注意的是堆中 \(k\) 表示的贡献不一定是原本价格,所以虽然 \(k<val_i\) 但因为我们是从大到小处理,所以 \(k\) 所对应的馅饼价格还是一定比 \(val_i\) 贵,也就是说它依然可以提供一次白嫖当前馅饼的机会。
一个细节,每次新加入的贡献不能直接加入堆中,原理很简单,处理当前馅饼时,堆中只能有之前馅饼的贡献,所以需要先放入一个栈中转存一下,处理结束了再加入堆。最后堆里元素的和就是所有白嫖的馅饼。
code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=500005;
char buf[1<<21],*p1,*p2;
inline char gc(){return p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++;}
inline ll rd()
{
char c;ll f=1;
while((c=gc())<'0'||c>'9')if(c=='-')f=-1;
ll x=c^48;
while('0'<=(c=gc())&&c<='9')x=(x<<1)+(x<<3)+(c^48);
return x*f;
}
ll n,ans,a[N];
ll val[N],c[N],cnt;
ll st[N],top;
priority_queue<ll,vector<ll>,greater<ll> >q;
int cmp(ll a,ll b){return a>b;}
int main()
{
n=rd();
for(int i=1;i<=n;i++)
ans+=(a[i]=rd());
sort(a+1,a+n+1,cmp);a[0]=1e18;
for(int i=1;i<=n;i++)
{
if(a[i]!=a[i-1]) val[++cnt]=a[i];
c[cnt]++;
}
ll p,tot,num=0;
for(int i=1;i<=cnt;i++)
{
p=min(num-2*(ll)q.size(),c[i]);
tot=min(c[i],num)-p;
for(int j=1;j<=p;j++)
st[++top]=val[i];
for(int j=1;j<=tot;j+=2)
{
int k=q.top();
q.pop();
if(k<val[i])
{
st[++top]=val[i];
if(j<tot) st[++top]=val[i];
}
else
{
st[++top]=k;
if(j<tot&&2*val[i]-k>=0)
st[++top]=2*val[i]-k;
}
}
while(top) q.push(st[top--]);
num+=c[i];
}
while(!q.empty())
ans-=q.top(),q.pop();
cout<<ans;
return 0;
}

浙公网安备 33010602011771号