【贪心 && 题解】 Buy One, Get One Free

题目描述:

在这里插入图片描述


Solution

首先,我们将问题转化: 最 少 化 花 钱 数 = 最 大 化 免 费 显 卡 数 最少化花钱数 = 最大化免费显卡数 =
于是,我们将问题转化为如何求最大化的显卡数。

由于相同之间的显卡不能互相转换(意思能理解吧),所以我们需要将同价值的显卡存放在一起,并记录数量。
即一个二元组 ( 价 值 , 数 量 ) (价值,数量) ()
由于购买了一个显卡之后,我们可以获得价格小于他的任意显卡,所以我们需要将显卡从大到小排序一遍。

对于我们免费获得的显卡,我们用一个小根堆来维护。

我们考虑第 i i i类显卡如何更新答案:
设第 i i i类显卡价值为 p i p_i pi,数量为 m i m_i mi,价值比他大的数的数量为 p r e pre pre,当前小根堆里面的免费显卡数量为 S i z e Size Size
1 、 1、 1对于当前显卡,我们能免费领,则免费领。问题是我们最多能领取多少呢?

  • 我们发现,我们维护了 p r e pre pre表示比他价值大的显卡的个数,而此时已经有 S i z e Size Size个数被放进了堆,而因为一张免费显卡对应一件被买的物品,因此前面被使用的数的个数为 2 ∗ S i z e 2*Size 2Size,所以当前可以换取的免费的显卡 f r e fre fre m a x ( m i n ( p r e − 2 ∗ S I z e , m i ) , 0 ) max(min(pre-2*SIze,m_i),0) max(min(pre2SIze,mi),0)

2 、 2、 2这一类显卡总共有 m i m_i mi个,其中免费领取 f r e fre fre个,因此我们需要买的数量为 m i − f r e m_i - fre mifre个。对于当前物品是否需要买,或者是否放入堆,我们分两种情况讨论(假设当前堆顶价值为 p ′ p' p):

I 、 I、 I p > p ′ p>p' p>p。对于这种情况,我们可以改买 p ′ p' p,而原本 p ′ p' p是通过免费显卡来获得的,这时候我们不仅将他取消了免费显卡,还又买了一张,因此这一下就多了两张显卡,这两张显卡可以换两张当前显卡。(至于为什么 p ′ < p p'<p p<p还可以换取,第 I I II II类会给予解释)。

I I 、 II、 II如果 p < = p ′ p<=p' p<=p 2 ∗ p > p ′ 2*p>p' 2p>p。如果我们用 p ′ p' p换取 2 ∗ p 2*p 2p,看似我们赚了 2 ∗ p − p ′ 2*p-p' 2pp元钱,但是我们的显卡却亏了一张。为什么呢?对于当前物品,我们用买一张 p ′ p' p来换取本需要买两张的 p p p,原本的免费显卡自然由两张减少为1张,因此对未来的影响为止。对于这种情况,我们还需要在堆里面在加入一个虚拟免费显卡 2 ∗ p − p ′ 2*p-p' 2pp(即可反悔式贪心)。如果取当前显卡,即相当于仍然保留免费显卡 p ′ p' p,否则就相当于用 p ′ p' p换取 2 ∗ p 2*p 2p( p ′ + 2 ∗ p − p ′ = 2 ∗ p p' + 2*p-p'=2*p p+2pp=2p)。对于这种情况,我们会用第 I I I种情况去更新,而第 I I I种情况也只有当前的虚拟显卡。为什么呢?因为我们当前是按照价格进行降序排序的,无特殊情况下前面入堆的数只可能比当前数要大,不可能小。所以对于第 I I I种情况,只有用虚拟显卡去更新,因此当前节点是用来反悔的,自然不需要考虑两者之间的大小关系。

3 、 3、 3最终堆中的所有元素的和就是目前最大的免费显卡的价值。只需要用所有数的价值和减去免费显卡价值和即可。

另外,还需要注意的一个细节是,当前需要入堆的数需要存在一个 v e c t o r vector vector或者其他数组中,因为当前状态无法更新状态。最后在入堆


Code

#include<bits/stdc++.h>
using namespace std;

const int N = 5e5+10;
int n;
int a[N];
priority_queue < int , vector < int > , greater < int > > q;
vector < int > now; 
int pre;
int p[N] , m[N];
int cnt = 0;
long long sum = 0;

bool mycmp(int x,int y){
	return x > y;
}

int main(){
	freopen("market.in","r",stdin);
	freopen("market.out","w",stdout);
	scanf("%d",&n);
	for (int i = 1; i <= n; i++) scanf("%d",&a[i]) , sum+=1ll*a[i];
	sort(a+1,a+n+1,mycmp);
	for (int i = 1; i <= n; i++)
	  if (a[i]!=a[i-1]) p[++cnt] = a[i] , m[cnt] = 1;
	  else m[cnt]++;//将相同价格的显卡记录
	for (int i = 1; i <= cnt; i++){
		int Size = q.size();
		int fre = max(0,min(pre - 2*Size , m[i]));//免费的显卡
		for (int j = 1; j <= fre; j++) now.push_back(p[i]);
		int by = m[i] - fre;
		for (int j = 1; j <= by; j+=2){//由于一张换两张,因此加二
			if (q.empty()) break;int pp = q.top();q.pop(); 
			if (p[i] > pp) {now.push_back(p[i]);if(j!=by) now.push_back(p[i]);continue;}
			now.push_back(pp);
			if (2*p[i] >= pp && j!=by) now.push_back(2*p[i] - pp);
		}//分两种情况维护
		for (int j = 0; j < now.size(); j++) q.push(now[j]);now.clear();
		pre+=m[i];//累加个数
	}
	long long Fr = 0;
	while (!q.empty()) Fr+=q.top() , q.pop();
	printf("%lld",sum - Fr);
	return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值