【简●解】[USACO] 照片Photo

【题目大意】

\(1\)~\(N\)的序列上有\(M\)个区间,使得这\(M\)个小区间每个覆盖了且仅覆盖了一个点,求最多点数,如果无解,输出\(-1\)

【分析】

刚开始做的时候我是懵的。。。不管三七二十一开始想差分约束系统,无奈没那么好的思维,没想出来。。。
一翻题解,,\(WOC!!!\)这是什么神仙\(DP?\)真的是学到了。。。

我们设\(f[i]\)表示序列的第\(i\)位是一个点时,序列\(1\)~\(i\)的最多点数。

那么\(dp\)方程就很好推了:设\(j\)<\(i\),即有\(f[i]=max(f[j]+1, f[i])\)

现在我们需要解决的是\(j\)的范围,也就是\(f[i]\)能从哪些状态中转移过来。

我们想,如果第\(i\)位是一个点,那么所有包含这个点的区间都不能再有点,也就是说,\(j\)必须小于包含第\(i\)位这个点的区间中最小的左端点
同样的,每个区间上必须有一个点,所以,\(j\)必须大于或等于不包含第\(i\)位这个点的区间中最大的左端点

至于怎么求,大家可以\(YY\)了。

我们会惊奇地发现\(DP\)方程满足单调性,于是单调队列走起。

细节见代码。

至于差分约束的做法嘛,,,以后会补的,,,咕咕咕

【code】

//#include<bits/stdc++.h>
#pragma GCC optimize("O3")
#pragma GCC optimize("O2")
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAX = 2000000 + 5;
inline int read(){
	int f = 1, x = 0;char ch;
	do { ch = getchar(); if (ch == '-') f = -1; } while (ch < '0'||ch>'9');
	do {x = x*10+ch-'0'; ch = getchar(); } while (ch >= '0' && ch <= '9'); 
	return f*x;
}
int n, m, l[MAX], r[MAX], f[MAX], ans, q[MAX];
int main(){
//	freopen("aaa.in", "r", stdin); 
//	freopen("aaa.out", "w", stdout); 
	int n = read(), m = read();
	for (int i = 1;i <= n + 1; ++i) r[i] = i - 1;
	for (int i = 1;i <= m; ++i) {
		int x = read(), y = read();
		r[y] = min(r[y], x - 1);
		l[y + 1] = max(l[y + 1], x);
	}
	for (int i = n; i > 0; --i) r[i] = min(r[i], r[i + 1]);
	for (int i = 2; i < n + 2; ++i) l[i] = max(l[i], l[i - 1]);	
	int j = 1, h = 1, t = 1; q[1] = 0;
	for (int i = 1;i <= n + 1; ++i) {
		while (j <= r[i] && j <= n) {
			if (f[j] == -1) {
				++j;
				continue;
			}
			while (f[j] > f[q[t]] && h <= t) --t;
			q[++t] = j;
			++j;
		}
		while (q[h] < l[i] && h <= t) ++h;
		if (h <= t) f[i] = f[q[h]] + (i != n+1 ? 1 : 0);
		else f[i] = -1;
	}
	printf("%d", f[n + 1]);
	return 0;
}
posted @ 2019-05-03 10:45  SilentEAG  阅读(345)  评论(2编辑  收藏  举报