题面
题意
你要买n个东西,当你买了一个价格为x的东西时,可以让商店免费赠送你一个价格严格小于x的东西,则你买下这n个东西至少要花多少钱。
做法
n很大,考虑贪心。
首先根据商品的代价从大到小排序,价格相同的商品一起考虑。
可以把所有赠送的商品的价格都放到一个小根堆里,当我们考虑价格为x的商品时,记一共有sum个商品的价格比x大,首先将可以送的商品直接赠送(共有
min
(
s
u
m
−
2
∗
p
q
.
s
i
z
e
(
)
,
c
n
t
[
x
]
)
\min(sum-2*pq.size(),cnt[x])
min(sum−2∗pq.size(),cnt[x])个),然后考虑剩下的价格为x的商品。
若此时堆首的商品价格大于x,令堆首的商品价格为y,则若买下y,可以多赠送两个x。
如果
2
∗
x
<
y
2*x<y
2∗x<y,显然不考虑买下y。
反之,则考虑买下y,这样原来是买下某件商品a,送了y,并买下了2个x,变成了买下了a和y送了2个x,后者虽然更省钱,但少送了两个更廉价的商品,因此可以把后者看作是多送了一个价格为
2
∗
x
−
y
2*x-y
2∗x−y的商品,将
2
∗
x
−
y
2*x-y
2∗x−y也加入堆中。
若此时堆首的商品价格小于x,则替换,改为买那件商品并赠送至多两个x。
最后堆中的元素和即为剩下的钱。
代码
#include<bits/stdc++.h>
#define ll long long
#define N 500100
using namespace std;
ll n,sum,bb,top,ans,num[N],cnt[N],b[N],sta[N];
priority_queue<ll,vector<ll>,greater<ll> >pq;
inline bool cmp(ll u,ll v){return u>v;}
int main()
{
ll i,j,t,mx;
cin>>n;
for(i=1;i<=n;i++) scanf("%lld",&num[i]),ans+=num[i];
sort(num+1,num+n+1,cmp);
for(i=1;i<=n;i++)
{
if(i==1 || num[i]!=num[i-1]) b[++bb]=num[i];
cnt[bb]++;
}
for(i=1;i<=bb;i++)
{
top=0;
mx=min(sum-(ll)pq.size()*2,cnt[i]);
for(j=1;j<=mx;j++) sta[++top]=b[i];
mx=min(sum,cnt[i])-mx;
for(j=1;j<=mx;j+=2)
{
t=pq.top();pq.pop();
if(t<b[i])
{
sta[++top]=b[i];
if(j<mx) sta[++top]=b[i];
}
else
{
sta[++top]=t;
if(j<mx && 2*b[i]>t) sta[++top]=2*b[i]-t;
}
}
for(j=1;j<=top;j++) pq.push(sta[j]);
sum+=cnt[i];
}
for(;!pq.empty();pq.pop()) ans-=pq.top();
cout<<ans;
}