[CF335F]Buy One, Get One Free

我被 S i s t e r \color{black}{S}\color{red}{ister} Sister 问了这道题,从此再也没学会贪心。

题目

传送门 to luogu

思路

直接贪心是错的,因为权值相等的时候不能 get one free \text{get one free} get one free

考虑一个类似 d p \tt dp dp 方法,记录全价购买物品的数量。我们画个图出来,用 x x x 坐标表示之前的全价物品数量, y y y 轴表示该物品的全价购买数量。那么转移的要求形如 y ⩾ k 0 − 2 x y\geqslant k_0-2x yk02x 。特别地,虽然 x x x 定义域不从 0 0 0 开始,我们通过平移,在图中也画成 0 0 0 开始即可。

x + y = λ x+y=\lambda x+y=λ 显然是等效线。我们只需要找到每条等效线上的 min ⁡ c o s t \min cost mincost

先假定 ∂ ∂ x \partial\over\partial x x 是随 x x x 增大而增大的,即在 x x x 方向上的 Δ c o s t \Delta cost Δcost 不降。在最后我们会完成证明。那么我们很容易找到不考虑 y ⩾ k 0 − 2 x y\geqslant k_0-2x yk02x 时的最优点,就是在 ∂ ∂ x {\partial\over\partial x} x v v v 大小关系变化的那一列。

如下图。橙线标示了定义域( i n c l u s i v e \rm inclusive inclusive)而上方绿线经过了所有最小值点,青色的线是等效线,而 x x x 轴上的绿线标示了 Δ c o s t < v \Delta cost<v Δcost<v 的位置。

在这里插入图片描述

横线也是定义域,即当前物品的全价购买数目不可能超过 c n t v cnt_v cntv 。所以绿线的末端会被 “挤” 成水平的。

但因为定义域的存在,某些等效线上的答案并未被找出,比如图中靠下的青色线。它所对应的最小值点显然在 ( 2 , 1 ) (2,1) (2,1),即最左端,因为它的 ∂ ∂ x \partial \over\partial x x 大于 v v v,所以 x x x 越小越好。

我们从 ( 1 , 3 ) (1,3) (1,3) 开始,找出更低的等效线,但显然要 c o s t cost cost 也变小。若当前点是 ( x , y ) (x,y) (x,y),从图中不难看出,下一个等效线的答案在 ( x + 1 , y − 2 ) (x{+}1,y{-}2) (x+1,y2) 位置。我们不断移动 ( x , y ) (x,y) (x,y),直到这样做的 Δ c o s t ⩾ 0 \Delta cost\geqslant 0 Δcost0 。注意到 Δ c o s t \Delta cost Δcost 是不降的,因此此时就不必再移动(找更低的等效线)了。

移动的时候的 Δ c o s t \Delta cost Δcost 就是新的 ∂ ∂ x \partial\over\partial x x,比较方便。

特别提醒,画图是容易出问题的,因为有时候它不能涵盖所有情况。

有可能 ∂ ∂ x < v {\partial\over\partial x}<v x<v 的部分超出了定义域的限制,如图。

在这里插入图片描述

此时的最优点应该在红线上。有趣的是,其与绿线上的点的 Δ c o s t \Delta cost Δcost 的可重集是相同的,只不过红线的结果(真实值)应该是有序的。

若令 x x x 定义域外的 Δ c o s t = + ∞ \Delta cost=+\infty Δcost=+,可以认为 x x x 只有下界。因此没有更多情况。

我们现在证明 结论。可以看到,在等效线之间的移动,和直接得到的绿线(全局最优点),都满足 Δ c o s t \Delta cost Δcost 单增。由归纳法知该结论成立。

如何实现这个过程呢?从橙线 y y y 轴的交点开始,沿着线移动,就可以找到初始点。该过程对 Δ c o s t \Delta cost Δcost 集合的影响是:将原来小于 v v v Δ c o s t \Delta cost Δcost 删去,然后加入两个 v v v 。特别地,如图二中 ( 2 , 1 ) ⇝ ( 3 , 0 ) (2,1)\leadsto(3,0) (2,1)(3,0),此时只加入单个 v v v

如果 y ⩾ 2 y\geqslant 2 y2,就可以尝试找更低的等效线。每次的 Δ c o s t \Delta cost Δcost 就是 2 v − ∂ ∂ x 2v-{\partial\over\partial x} 2vx,直接把它加入 Δ c o s t \Delta cost Δcost 集合。直到无法移动,就结束了。

如果 y = 0 y=0 y=0,就要把得到的 Δ c o s t \Delta cost Δcost 排序,因为我们要得到图二的红线。但这不需要 i f \tt if if 语句,因为其他情况下就是天然有序。

一种简单的实现就是用堆。每次最多移动 c n t v cnt_v cntv 次,总复杂度 O ( n log ⁡ n ) \mathcal O(n\log n) O(nlogn)

另外要注意的是:对于堆中需要新加入的元素,请在该权值处理结束后加入,否则就会出问题。

代码

#include <cstdio>
#include <iostream>
#include <cstring>
#include <vector>
#include <algorithm>
#include <queue>
using namespace std;
typedef long long int_;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
inline int readint(){
	int a = 0; char c = getchar(), f = 1;
	for(; c<'0'||c>'9'; c=getchar())
		if(c == '-') f = -f;
	for(; '0'<=c&&c<='9'; c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}

const int MaxN = 500005;
int a[MaxN], b[MaxN];
priority_queue< int,vector<int>,greater<int> > pq;

int main(){
	int n = readint();
	rep(i,1,n) a[i] = readint();
	sort(a+1,a+n+1,greater<int>());
	int one = 0; // how many 1
	int_ ans = 0; // current answer
	for(int i=1,nxt=1; i<=n; i=nxt){
		for(; nxt<=n&&a[nxt]==a[i]; ++nxt);
		int now = max(nxt-i-one,0), top = 0;
		rep(j,1,nxt-i-now) b[++ top] = a[i];
		ans += 1ll*now*a[i]; one -= nxt-i-now;
		while(now && !pq.empty() && pq.top() < a[i]){
			-- now, ++ one; b[++ top] = a[i];
			ans -= a[i]-pq.top(); pq.pop();
			if(now) -- one, b[++ top] = a[i], ans -= a[i];
		}
		while(now >= 2 && !pq.empty() && pq.top() < 2*a[i]){
			now -= 2; int t = 2*a[i]-pq.top();
			b[++ top] = pq.top(); // not deleted!!!
			ans -= t, pq.pop(); b[++ top] = t;
		}
		rep(j,1,top) pq.push(b[j]);
		one += now; // new 1
	}
	printf("%lld\n",ans);
	return 0;
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值