洛谷 P4182
首先把所有线段按左端点升序,左端点相同按右端点升序排序。发现如果一个线段完全被另一个线段包含,那把它去掉也没有影响。于是我们先去掉这种线段,然后进行 DP 。设 \(f_{i,j}\) 表示前 \(i\) 个区间删去 \(j\) 个,且钦定第 \(i\) 个区间不删除的最大覆盖点数。那么 \(f_{i,j}=\max\limits_{k\lt i}(f_{k,j-(i-k-1)}+r_i-\max(l_i,r_k))\) 。简单解释一下,就是钦定上一个选的是 \(k\) ,\((k,i)\) 间的全都不选。因为 \(i-k-1\le K\) ,所以复杂度为 \(\mathcal O(nK^2)\)。
对于 \(k\) 比较小,即 \(r_k\lt l_i\) 时的情况,\(f_{i,j}=\max(f_{k,j-(i-k-1)}+r_i-l_i)\)。如果我们能对于每一个 \(i-j-1\) 预处理出 \(f_{k,k-(i-j-1)}\) 的最大值 \(p\) ,就可以 \(\mathcal O(1)\) 转移了。
对于 \(k\) 比较大,即 \(r_k\gt l_i\) 时的情况,\(f_{i,j}=\max(f_{k,j-(i-k-1)}+r_i-r_k)\)。我们用单调队列维护 \(f_{k,j-(i-k-1)}-r_k\) 的最大值。当单调队列中的元素不满足 \(r_k\gt l_i\) 时就弹出,弹出的时候顺便处理出 \(p\)。
总结一下,我们需要处理出 \(r_k\lt l_i\) 时 \(f_{k,k-(i-j-1)}\) 的最大值,再开 \(n\) 个单调队列,第 \(i-j\) 个单调队列维护 \(f_{i,j}-r_i\) 的最大值。这样复杂度就降为了 \(\mathcal O(nK)\)。
Code:
#include <bits/stdc++.h>
using namespace std;
#define fi first
#define se second
typedef pair <int, int> pii;
const int N = 100005, M = 105;
int n, k;
pii a[N], b[N];
int f[N][M], p[N];
deque <int> q[N];
int main() {
scanf("%d%d", &n, &k);
for (int i = 1; i <= n; ++i) scanf("%d%d", &a[i].fi, &a[i].se);
if (k >= n) return printf("%d", 0), 0;
sort(a + 1, a + n + 1, [](pii x, pii y) {
return x.fi != y.fi ? x.fi < y.fi : x.se < y.se;
});
int mx = -1, tot = 0;
for (int i = 1; i <= n; ++i) {
if (a[i].se > mx) b[++tot] = a[i];
else --k;
mx = max(mx, a[i].se);
}
k = max(k, 0), b[++tot] = pii(1e9, 1e9);
for (int i = 1; i <= tot; ++i)
for (int j = 0; j <= min(i - 1, k); ++j) {
int t = i - j - 1;
while (q[t].size() && b[q[t].front()].se < b[i].fi) {
p[t] = max(p[t], f[q[t].front()][q[t].front() - t]);
q[t].pop_front();
}
f[i][j] = max(f[i][j], p[t] + b[i].se - b[i].fi);
if (q[t].size()) f[i][j] = max(f[i][j], f[q[t].front()][q[t].front() - t] + b[i].se - b[q[t].front()].se);
t = i - j;
while (q[t].size() && f[q[t].back()][q[t].back() - t] - b[q[t].back()].se <= f[i][j] - b[i].se) q[t].pop_back();
q[t].push_back(i);
}
printf("%d", f[tot][k]);
return 0;
}