CF335F Buy One, Get One Free

题目大意

你到一家正在进行特价活动的馅饼店买馅饼。规则是每全价购买一个馅饼,都可以免费得到一个价格严格更低的馅饼。
求出为所有馅饼支付的最小花费。
\(N\le 5e5,A_i\le 1e9\)

做法

自己能想到的最优做法就是\(O(n^2)\)
考虑dp 一定从大到小 每次选择一段 如果左半部分可以覆盖右半部分就更新 没有单调性也没有优化空间
题解的做法是可反悔贪心 转化问题为免费拿到的和尽可能大
也是从大到小排序 接着考虑怎么维护这个反悔过程 考虑维护免费拿过的集合的决策贡献
一样的一起处理 比如有\(cnt\)\(a\) 前面有\(s\)个馅饼 \(c\)个免费拿的 还剩\(s-2*c\)个和当前配对的
讨论\(s-2*c\ge 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\n",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\n",f[n]);
    return 0;
}*/
posted @ 2021-04-24 15:32  寒雨微凝  阅读(226)  评论(0编辑  收藏  举报