【题解】[USACO] 照片Photo

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

猜你喜欢

转载自www.cnblogs.com/silentEAG/p/10804519.html