把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

P3084 [USACO13OPEN] Photo G 分析

题目概述

题目链接:https://www.luogu.com.cn/problem/P3084

\(n\) 个牛,拍了 \(m\) 张照片,拍了 \([a_i,b_i]\) 中的牛,现在牛群中有一些特殊的牛,而且每一张照片有且仅有一个特殊的牛。问最多有多少特殊的牛。

分析

一开始会想到跟区间扯上关系,但我们只需要将这个区间看作一个限制条件即可。

即:在 \([a_i,b_i]\) 之间的牛满足:

  • 至少有一头特殊的牛
  • 最多有一头特殊的牛

然后,因为我们把这个看作为条件那我们在条件之外直接 \(dp\)

即设 \(f_i\) 表示前 \(i\) 头牛最多有多少特殊的牛。

我们不知道是不是要选择这头牛。

事实上可以直接设 \(f_i\) 表示第 \(i\) 头牛是特殊的牛,那么目前最多有多少特殊的牛?

显然:

\[f_i=\max f_j+1 \]

这个转移 \(j\) 是有条件的:

  • 不在所有包含 \(i\) 的区间内(满足条件 \(2\)
  • \(i\) 之间不能有一个独立的区间不包含他们两个(满足条件 \(1\))。

对于一个区间 \([a_i,b_i]\),那么 \(b_i+1\) 及后面的点决策点一定会 \(\geq a_i\)(满足条件 \(1\)),那么对于 \([a_i,b_i]\) 的决策点一定会 \(<a_i\)

我们发现前面那一个是好做的,赋值之后直接前缀 \(max\) 就行了。

后面的那一个可以你可以想象一下,我 \([1,a_i]\) 的决策点是不是也一定会 \(<a_i\) 啊!

那跟前面那一个一样直接维护前缀即可。

然后就有了 \(\mathcal{O}(n^2)\) 的代码,但是可以过(doge)。

#include <iostream>
#include <cstdio>
#include <stdlib.h>
#include <cstring>
#include <algorithm>
#include <vector>
#define int long long
#define N 200005
using namespace std;
int n,m;
int f[N],mn[N],mx[N];
signed main(){
    cin >> n >> m;
    for (int i = 0;i <= n + 1;i ++) mn[i] = i;
    int l,r;
    while(m --){
        scanf("%lld%lld",&l,&r);
        mn[r] = min(mn[r],l - 1);
        mx[r + 1] = max(mx[r + 1],l);
    }
    for (int i = 1;i <= n + 1;i ++) mx[i] = max(mx[i - 1],mx[i]);
    for (int i = n;i;i --) mn[i] = min(mn[i + 1],mn[i]);
    for (int i = 1;i <= n + 1;i ++) {
        l = mx[i],r = mn[i];
        while(l <= r && l < i) f[i] = max(f[i],f[l ++] + 1);
        if (f[i] == 0) f[i] = -1;
    }
    if (f[n + 1] > 1) f[n + 1] --;
    cout << f[n + 1];
    return 0;
}

考虑怎么变成 \(\mathcal{O}(n)\) 或者一个理论可行的复杂度。

注意到 \(l\) 是不断变大的, \(r\) 也是。

那不就成了一个滑动窗口吗?

拿下!

代码

时间复杂度 \(\mathcal{O}(n)\)

#include <iostream>
#include <cstdio>
#include <stdlib.h>
#include <cstring>
#include <algorithm>
#include <vector>
#define int long long
#define N 200005
using namespace std;
int n,m;
int f[N],mn[N],mx[N],q[N];
signed main(){
    cin >> n >> m;
    for (int i = 0;i <= n + 1;i ++) mn[i] = i;
    int l,r;
    while(m --){
        scanf("%lld%lld",&l,&r);
        mn[r] = min(mn[r],l - 1);
        mx[r + 1] = max(mx[r + 1],l);
    }
    for (int i = 1;i <= n + 1;i ++) mx[i] = max(mx[i - 1],mx[i]);
    for (int i = n;i;i --) mn[i] = min(mn[i + 1],mn[i]);
    int head = 1,tail = 0;
    q[++tail] = 0;
    for (int i = 1;i <= n + 1;i ++) {
        // l = mx[i],r = mn[i];
        // while(l <= r && l < i) f[i] = max(f[i],f[l ++] + 1);
        // if (f[i] == 0) f[i] = -1;
        l = mx[i],r = mn[i];
        int j = q[tail] + 1;
        for (;j <= r && j < i;j ++) {
            while(head <= tail && f[q[tail]] < f[j]) tail --;
            q[++tail] = j;
        }
        while(head <= tail && q[head] < l) head ++;
        if (head > tail) f[i] = -1;
        else f[i] = f[q[head]] + 1;
        if (f[i] <= 0) f[i] = -1;
    }
    if (f[n + 1] > 0) f[n + 1] --;
    cout << f[n + 1];
    return 0;
}
posted @ 2025-10-16 15:28  high_skyy  阅读(10)  评论(0)    收藏  举报
浏览器标题切换
浏览器标题切换end