(改编自杜教的「candy」)
题面
👳:1💰 = 6🥖? 哼~ ~
🧔:🧟
👳:……
S t e v e \tt{Steve} Steve 在出生点处的村庄找制图师买到了一张林地探险家地图,指向了 x x x 轴正方向上很远的地方的一处黑森林中的林地府邸, S t e v e \tt{Steve} Steve 打算去那里探险。
沿着 x x x 轴有 n n n 个村庄,编号为 0 ∼ n − 1 0\sim n-1 0∼n−1 ,坐标分别为 ( a 0 , − , 0 ) , ( a 1 , − , 0 ) , . . . , ( a n − 1 , − , 0 ) (a_0,-,0),(a_1,-,0),...,(a_{n-1},-,0) (a0,−,0),(a1,−,0),...,(an−1,−,0) 。 S t e v e \tt{Steve} Steve 所在的就是 0 0 0 号村庄,也就是说 a 0 = 0 a_0=0 a0=0 ,而林地府邸的坐标是 ( a n , − , 0 ) (a_n,-,0) (an,−,0) 。 a 0 , a 1 , a 2 , . . . , a n a_0,a_1,a_2,...,a_n a0,a1,a2,...,an 依次递增,单位:区块。 S t e v e \tt{Steve} Steve 需要从 0 0 0 号村庄出发,依次经过 1 , 2 , 3 , . . . , n − 1 1,2,3,...,n-1 1,2,3,...,n−1 号村庄,最终到达林地府邸。
由于路途崎岖, S t e v e \tt{Steve} Steve 每走一个区块就要消耗一个面包,因此食物是个问题。不过 S t e v e \tt{Steve} Steve 非常有钱,他带了半背包的潜影盒,全部装满了绿宝石块,钱足够。他打算在经过村庄时买卖面包(自残行为,请勿模仿)来解决食物问题。
无所不为的 S t e v e \tt{Steve} Steve 在各个村庄的风评都不一样,因此价格有所不同。具体地,第 i i i 个村庄的面包售价为 b u y i buy_i buyi 绿宝石/个,收购价为 s e l l i sell_i selli 绿宝石/个,交易数量不受限制。 S t e v e \tt{Steve} Steve 可以在每个村庄里买面包和卖面包。
处于战略考虑, S t e v e \tt{Steve} Steve 只能同时携带 C C C 个面包,目前在出生点的他没有面包。
S t e v e \tt{Steve} Steve 向你询问,为了到达林地府邸,最少要花费多少绿宝石。你没必要为他到达林地府邸后的战斗准备面包。
输入格式
第一行两个整数 n , C n,C n,C 。
接下来一行 n n n 个整数 a 1 , a 2 , . . . , a n a_1,a_2,...,a_n a1,a2,...,an ,保证两村庄间距以及最后一个村庄与林地府邸间的距离都不超过 C C C 。
接下来 n n n 行,每行两个整数 b u y i , s e l l i ( 0 ≤ i ≤ n − 1 ) buy_i,sell_i(0\leq i\leq n-1) buyi,selli(0≤i≤n−1) ,保证 s e l l i ≤ b u y i sell_i\leq buy_i selli≤buyi。
输出格式
一行,一个整数,表示答案。注意答案可能为负数, S t e v e \tt{Steve} Steve 可以赚差价。
样例输入
4 9
6 7 13 18
10 7
8 4
3 2
5 4
样例输出
105
数据范围
1 ≤ n ≤ 2 × 1 0 5 , C ≤ 1 0 6 , 1 ≤ a i ≤ 1 0 9 , 0 ≤ s e l l i ≤ b u y i ≤ 1 0 6 1\leq n\leq 2\times10^5,C\leq10^6,1\leq a_i\leq10^9,0\leq sell_i\leq buy_i\leq10^6 1≤n≤2×105,C≤106,1≤ai≤109,0≤selli≤buyi≤106 。
题解
注意到我们总的面包花费数是固定的,所以不妨往贪心方面想。
每当我们要吃面包的时候,尽量吃便宜的。如果可以在前面的村庄买便宜的面包,到后面的村庄赚钱,那么尽量赚,同时碰到收购价更高的村庄要有个反悔的过程。
我们可以每次先把容量 C C C 的购物车装满,在路上再下单。遇到卖价更低的村庄,就把购物车里价格高的面包都替换掉。遇到可赚差价的情况,就先把买卖做了,对村民承诺到了林地府邸就发货,同时把原先的购物车里的该面包价格篡改成这单生意的收购价。这样,当后来饥饿的时候,可以通过取消这单生意,付出「收购价」的代价获得一个面包,也可以在收购价更高的村庄反悔。
是不是很合理?这就是对的,想想就明白了。
于是单调队列的做法就诞生了:
我们用一个单调队列维护购物车里的每种面包「价格」和「数量」(二元组,从小到大),遇到新的村庄 i i i ,
- 把队列尾部所有价格大于 b u y i buy_i buyi 的面包全部弹出,
- 在队列尾部添加价格为 b u y i buy_i buyi 的面包,数量为 C − 当 前 面 包 数 C-当前面包数 C−当前面包数 。
- 若队列首的面包价格 x x x ,数量 y y y ,而 x < s e l l i x<sell_i x<selli ,那么把答案减去 ( s e l l i − x ) ⋅ y (sell_i-x)\cdot y (selli−x)⋅y ,然后弹出队首,重复此过程。统计此操作弹出的面包数量总数 c n t y cnty cnty,然后在队首加入价格为 s e l l i sell_i selli ,数量为 c n t y cnty cnty 的面包。
在经过地点之间一段长度为 L L L 区块的路时,弹出相应数量的队首面包,付出相应的价格。
到达目的地后就不管了,答案已经在路上算完了。
时间复杂度 O ( n ) O(n) O(n) 。
CODE
#include<set>
#include<map>
#include<queue>
#include<stack>
#include<vector>
#include<bitset>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define MAXN 200005
#define LL long long
#define ULL unsigned long long
#define DB double
#define lowbit(x) (-(x) & (x))
#define ENDL putchar('\n')
#define FI first
#define SE second
int xchar() {
static const int maxn = 1000000;
static char b[maxn];
static int pos = 0,len = 0;
if(pos == len) pos = 0,len = fread(b,1,maxn,stdin);
if(pos == len) return -1;
return b[pos ++];
}
//#define getchar xchar
LL read() {
LL f=1,x=0;int s = getchar();
while(s < '0' || s > '9') {if(s<0)return -1;if(s=='-')f=-f;s = getchar();}
while(s >= '0' && s <= '9') {x = (x<<3) + (x<<1) + (s^48); s = getchar();}
return f*x;
}
void putpos(LL x) {if(!x)return ;putpos(x/10);putchar('0'+(x%10));}
void putnum(LL x) {
if(!x) {putchar('0');return ;}
if(x<0) {putchar('-');x = -x;}
return putpos(x);
}
void AIput(LL x,int c) {putnum(x);putchar(c);}
int n,m,s,o,k;
int Min(int a,int b) {return a<b ? a:b;}
int a[MAXN];
int bu[MAXN],se[MAXN];
int hd,tl;
pair<int,int> st[MAXN];
int main() {
freopen("candy.in","r",stdin);
freopen("candy.out","w",stdout);
n = read();m = read();
for(int i = 1;i <= n;i ++) a[i] = read();
for(int i = 0;i < n;i ++) {
bu[i] = read(); se[i] = read();
}
hd = 1; tl = 0;
LL ans = 0;
int ct = 0;
for(int i = 0;i < n;i ++) {
while(hd <= tl && st[tl].FI > bu[i]) ct -= st[tl --].SE;
int cn = 0;
while(hd <= tl && st[hd].FI < se[i]) {
ans += (st[hd].FI - se[i]) *1ll* st[hd].SE;
cn += st[hd].SE; hd ++;
}
if(cn) st[-- hd] = make_pair(se[i],cn);
if(ct < m) st[++ tl] = make_pair(bu[i],m-ct);
ct = m;
int nm = a[i+1] - a[i];
while(nm) {
int dl = Min(nm,st[hd].SE);
ans += st[hd].FI *1ll* dl;
st[hd].SE -= dl; nm -= dl; ct -= dl;
if(st[hd].SE == 0) hd ++;
}
}
AIput(ans,'\n');
return 0;
}