【简●解】[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;
}