我被 S i s t e r \color{black}{S}\color{red}{ister} Sister 问了这道题,从此再也没学会贪心。
题目
思路
直接贪心是错的,因为权值相等的时候不能 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 y⩾k0−2x 。特别地,虽然 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 y⩾k0−2x 时的最优点,就是在 ∂ ∂ 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,y−2) 位置。我们不断移动 ( x , y ) (x,y) (x,y),直到这样做的 Δ c o s t ⩾ 0 \Delta cost\geqslant 0 Δcost⩾0 。注意到 Δ 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 y⩾2,就可以尝试找更低的等效线。每次的 Δ c o s t \Delta cost Δcost 就是 2 v − ∂ ∂ x 2v-{\partial\over\partial x} 2v−∂x∂,直接把它加入 Δ 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;
}