codeforces335F Buy One, Get One Free

题面

题意

你要买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(sum2pq.size(),cnt[x])个),然后考虑剩下的价格为x的商品。
若此时堆首的商品价格大于x,令堆首的商品价格为y,则若买下y,可以多赠送两个x。
如果 2 ∗ x < y 2*x<y 2x<y,显然不考虑买下y。
反之,则考虑买下y,这样原来是买下某件商品a,送了y,并买下了2个x,变成了买下了a和y送了2个x,后者虽然更省钱,但少送了两个更廉价的商品,因此可以把后者看作是多送了一个价格为 2 ∗ x − y 2*x-y 2xy的商品,将 2 ∗ x − y 2*x-y 2xy也加入堆中。
若此时堆首的商品价格小于x,则替换,改为买那件商品并赠送至多两个x。
最后堆中的元素和即为剩下的钱。

代码

#include
#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;
}

你可能感兴趣的:(贪心,经典,技巧,堆)