【NOI P模拟赛】出发·林地府邸(反悔贪心,单调队列)

(改编自杜教的「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 0n1 ,坐标分别为 ( a 0 , − , 0 ) , ( a 1 , − , 0 ) , . . . , ( a n − 1 , − , 0 ) (a_0,-,0),(a_1,-,0),...,(a_{n-1},-,0) (a0,,0),(a1,,0),...,(an1,,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,...,n1 号村庄,最终到达林地府邸。

由于路途崎岖, 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(0in1) ,保证 s e l l i ≤ b u y i sell_i\leq buy_i sellibuyi

输出格式

一行,一个整数,表示答案。注意答案可能为负数, 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 1n2×105,C106,1ai109,0sellibuyi106

题解

注意到我们总的面包花费数是固定的,所以不妨往贪心方面想。

每当我们要吃面包的时候,尽量吃便宜的。如果可以在前面的村庄买便宜的面包,到后面的村庄赚钱,那么尽量赚,同时碰到收购价更高的村庄要有个反悔的过程。

我们可以每次先把容量 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 (sellix)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;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值