P1868题解

传送门:https://www.luogu.com.cn/problem/P1868

区间重叠问题我们往往定位前一个区间右端点再通过判断当前区间左端点是否小于他来判断重叠。

解法一:

动态规划+线段树

我们以 \(dp_i\) 表示以 \(i\) 为右端点构成的区间集合最大值,我们按左端点排序,保证新加入的区间不能从中间插入之前区间的集合(这样会构成更优的结果,而遍历顺序导致不可能获得这种结果),这样我们就能得到一个转移方程。

设当前遍历到的区间为 \([l,r]\)

\[dp_r=\max\limits_{0\le j<l} (dp_j) + r-l+1 \]

此时状态下标为值域,需要离散化,本题数据范围比较小可以忽略。对于前面那坨区间 \(\max\) 用线段树动态维护即可。离散化后时间复杂度 \(O(n\log_2n)\),空间复杂度 \(O(n)\)

解法二:

动态规划+二分

上面的状态涉及值域没有优化空间,考虑设计一个更松的状态,令 \(dp_i\) 表示前 \(i\) 个区间可能组成集合的最大值。我们不关心具体选了哪个区间作为右端点,也不关心选择了什么,只需要保证转移合法即可。

同样,为了保证加入的区间不能在中间插入,我们仍然选择按中间一个端点排序。得到转移方程:

\(x\) 为对于区间 \(i\) 合法的最靠右的区间

\[dp_i=\max(dp_{i-1},dp_x+i_r-i_l+1) \]

\(\max\) 中分别是不选这个区间和选这个区间。

\(x\) 可以通过二分得到。

给出解法一的代码:

#include <bits/stdc++.h>

const int N = 3e6 + 1;

using namespace std;

int Max[N << 2];

int ls(int p) { return p << 1; }

int rs(int p) { return p << 1 | 1; }

void pushup(int p) { Max[p] = max(Max[ls(p)], Max[rs(p)]); }

void update(int p, int lp, int rp, int pos, int val) {
    if (lp == rp) {
        Max[p] = max(Max[p], val);
        return;
    }
    int mid = (lp + rp) >> 1;
    if (pos <= mid) update(ls(p), lp, mid, pos, val);
    else update(rs(p), mid + 1, rp, pos, val);
    pushup(p);
}

int query(int p, int lp, int rp, int l, int r) {
    if (lp >= l && rp <= r) return Max[p];
    int ans = 0;
    int mid = (lp + rp) >> 1;
    if (l <= mid) ans = query(ls(p), lp, mid, l, r);
    if (r > mid) ans = max(ans, query(rs(p), mid + 1, rp, l, r));
    return ans;
}

pair<int, int> a[N];

int main() {
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int n, V = 0;
    cin >> n;
    for (int i = 1; i <= n; ++i) cin >> a[i].first >> a[i].second, ++a[i].first, ++a[i].second, V = max(V, a[i].second);
    sort(a + 1, a + n + 1);
    for (int i = 1; i <= n; ++i)
        update(1, 0, V, a[i].second,
               query(1, 0, V, 0, a[i].first - 1) + a[i].second - a[i].first + 1);
    cout << query(1, 0, V, 0, V);
    return 0;
}
posted @ 2026-01-11 23:19  Jefferyzzzz  阅读(4)  评论(0)    收藏  举报