「USACO13OPEN」Photo「单调队列」

题目传送门

题解

看起来是差分约束,但是USACO出题人又卡SPFA了!

这里有一种巧妙的DP方法。

f [ i ] f[i] f[i] : i i i必须放,最多能放多少个.

考虑上一个可以放在哪里。
l [ i ] l[i] l[i] : 完全在 i i i左边的区间,左端点的最大值 (不能不放)
r [ i ] r[i] r[i] : 包含 i i i的区间中,左端点的最小值 − 1 -1 1 (只能放 1 1 1个)
这个怎么求呢?
给一个区间 [ x , y ] [x,y] [x,y],用 x − 1 x-1 x1去更新 r [ y ] r[y] r[y],用 x x x去更新 l [ y + 1 ] l[y+1] l[y+1]。最后 l l l取前缀最大值, r r r取后缀最小值。
(这个具体原因可以根据 l , r l,r l,r的定义思考一下)

于是:
f [ i ] = m a x ( f [ j ] ) + 1 , l [ i ] ≤ j ≤ r [ i ] f[i]=max(f[j])+1,l[i]\leq j \leq r[i] f[i]=max(f[j])+1,l[i]jr[i]
考虑到 l , r l,r l,r数组满足单调性,可以使用单调队列优化(线段树优化应该也行)

#include <cstdio>
#include <ios>

using std :: max;
using std :: min;

const int N = 200010;

int n, m, ans;
int l[N], r[N], f[N];
//l(i):完全在i左边的区间,左端点的最大值 (不能不放)
//r(i):包含i的区间中,左端点的最小值-1 (只能放1个) 
int main() {
	scanf("%d%d", &n, &m);
	for(int i = 1; i <= n + 1; i ++) r[i] = i - 1; //一个位置只能有一个点 
	for(int i = 1, x, y; i <= m; i ++) {
		scanf("%d%d", &x, &y);
		r[y] = min(r[y], x - 1);
		l[y + 1] = max(l[y + 1], x);
	}
	for(int i = n; i; i --) r[i] = min(r[i], r[i + 1]);
	for(int i = 1; i <= n; i ++) l[i] = max(l[i], l[i - 1]);
	static int hd = 0, bk = 1, q[N] = {0}, j = 1;
	for(int i = 1; i <= n + 1; i ++) {
		for(; j <= n && j <= r[i]; ++ j)
			if(~ f[j]) {
				for(; hd < bk && f[j] > f[q[bk - 1]]; -- bk) ;
				q[bk ++] = j;
			}
		for(; hd < bk && q[hd] < l[i]; ++ hd) ;
		f[i] = hd < bk ? f[q[hd]] + 1 : -1;
		if(~ f[i]) ans = max(ans, f[i]);
	}
	printf("%d\n", (~ f[n + 1]) ? f[n + 1] - 1 : -1);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值