题解 CF335F 【Buy One, Get One Free】
CF335F Buy One, Get One Free解题报告:
更好的阅读体验
题意
- 给定
n 个馅饼,每个价格为a_i 。每买一个馅饼都可以赠送一个价格严格小于它的馅饼,求买到所有馅饼的最小花费。 - 数据范围:
1\leqslant n\leqslant 10^5 。
分析
反悔贪心。
我们考虑选择若干个馅饼使得它们免费,答案为所有馅饼的总价减去免费的馅饼的价格之和。
首先把价格相同的馅饼合并,然后把馅饼按价格从大到小排序,那么现在每一次赠送都只能依赖前面买过的馅饼。
我们设之前有
我们考虑先贪心地把前面全部的全价购买的馅饼兑换完,也就是先得到
我们考虑把之前的所有兑换操作都保存在一个数据结构里,那么我们可以对于里面的某些馅饼进行反悔,并重新选择当前的馅饼。
我们考虑之前免费得到的某个价格为
-
-
-
- 如果单选
2s-k 这个馅饼就是全价买k ,把两个名额给s ;
- 如果单选
-
- 如果单选
k 就是不改变决策;
- 如果单选
-
- 如果同时选择
k,2s-k ,那么就是用其他的名额选择两个s 。
- 如果同时选择
你可能直接用了个数组保存可以反悔的决策,然而你会在
7
10 9 5 5 4 3 2
你选取了
实际上我们选取
时间复杂度:我们考虑每次枚举堆内的馅饼都会把某个决策
代码
注意,这里的小根堆用权值取负实现。
#include<stdio.h>
#include<algorithm>
#include<queue>
#define int long long
using namespace std;
const int maxn=500005;
int n,ans,tot,sum;
int a[maxn],s[maxn],c[maxn],tmp[maxn];
priority_queue<int>q;
inline int cmp(int a,int b){
return a>b;
}
signed main(){
scanf("%lld",&n);
for(int i=1;i<=n;i++)
scanf("%lld",&a[i]),ans+=a[i];
sort(a+1,a+1+n,cmp);
for(int i=1;i<=n;i++){
if(i==1||a[i]!=a[i-1])
s[++tot]=a[i],c[tot]++;
else c[tot]++;
}
for(int i=1;i<=tot;i++){
int ok=min(sum-(int)q.size()*2,c[i]),tmps=0,rst=min(c[i]-ok,sum-ok);
for(int j=1;j<=ok;j++)
tmp[++tmps]=s[i];
for(int j=1;j<=rst;j+=2){
int k=-q.top();
q.pop();
if(k<s[i]){
tmp[++tmps]=s[i];
if(j<rst)
tmp[++tmps]=s[i];
}
else{
tmp[++tmps]=k;
if(j<rst)
tmp[++tmps]=2*s[i]-k;
}
}
for(int j=1;j<=tmps;j++)
if(tmp[j]>=0)
q.push(-tmp[j]);
sum+=c[i];
}
while(!q.empty())
ans+=q.top(),q.pop();
printf("%lld\n",ans);
return 0;
}