BZOJ5185 [USACO18JAN] Lifeguards P 题解
由于这个跟区间的取舍相关,所以可以考虑dp.
想到设\(dp[i][j]\)为在前i个区间中删掉j个并且第i个必取的最优值.
那么显然状态转移方程为:
\[dp[i][j] = max(dp[i][j], dp[x][j-(i-x-1)] +v(x, i))
\]
其中,\(v(x, i)\)为加上第i个区间后新增的贡献,因为区间i可能和区间k重合,所以\(v(x, i)\)并不一定是i的长度.
但是...这个dp的时间复杂度是\(O(NKK)\)
所以我们要开单调队列优化.
这里的单调队列用
考虑到如果之前的一个dp[x][y]能更新dp[i][j],那么x - y = i - j - 1;
而又因为更新可分为区间x和区间i重叠以及不重叠2种:
- 重叠:更新为dp[x][y] + i的右端点坐标 - x的右端点坐标.
可以看出,如果要使答案最大,我们一定只会选取 (dp[x][y] - x的右端点坐标) 最大的那一个.所以对于重叠的区间,可以用单调(不增)队列维护 (dp[x][y] - x的右端点坐标) 的值,每次只要选取队头的元素做贡献(加上区间i的右端点坐标)即可. - 不重叠:更新为:dp[x][y] + i的右端点 - i的左端点.
对于这个来说,我们将单调队列的队首一直弹出直到队首区间有和区间i重叠, 对于这些没有重叠的元素, 因为维护的是(dp[x][y] - x的右端点坐标),所以还要将其加上x的右端点坐标,再取max后加上区间i的贡献.
最后区间i算完后,要把区间i也像这样将(dp[i][j] - i的右端点坐标)加入单调队列.
而这样的单调队列有多个, 我们用i-j作为区间dp[i][j]的dp[x][y] - x的右端点坐标
的单调队列的关键字,即dp[i][j]对应单调队列\(i-j\);
最后再求一下答案就好了,说到底还是dp.
送上代码:
#include <bits/stdc++.h>
using namespace std;
struct Node { int l, r; };
struct Node2 { int id, val; };
const int MAXN = 1e5 + 10;
const int MAXK = 1e2 + 10;
int N, K, p[MAXN], dp[MAXN][MAXK];
Node a[MAXN], b[MAXN];
deque<Node2> q[MAXN];
bool cmp(Node x, Node y) { return x.l == y.l? x.r > y.r : x.l < y.l; }
int main() {
cin >> N >> K;
for(int i = 1; i <= N; ++i)
scanf("%d%d", &a[i].l, &a[i].r);
sort(a + 1, a + N + 1, cmp);
int cnt = 0, lst = -1;
for(int i = 1; i <= N; ++i) {
if(a[i].r > lst) b[++cnt] = a[i];
else K--;
lst = max(lst, a[i].r);
}
N = cnt, K = K < 0 ? 0 : K;
for(int i = 1; i <= N; ++i)
for(int j = 0; j < min(K + 1, i); ++j) {
int now = i - j - 1;
while(!q[now].empty() && (b[q[now].front().id].r < b[i].l)) {
Node2 to = q[now].front();
p[now] = max(p[now], to.val + b[to.id].r);//加上b[to.id].r的原因是维护队列时减去了b[to.id].r;
q[now].pop_front();
}
dp[i][j] = max(dp[i][j], p[now] + b[i].r - b[i].l);//不重合的就取最大值
if(!q[now].empty())
dp[i][j] = max(dp[i][j], q[now].front().val + b[i].r);//重合的区间由于是单调队列,第一个即最大值.
int n = dp[i][j] - b[i].r;
now = i - j;
while(!q[now].empty() && (q[now].back().val < n))//将dp[i][j]所对应的单调队列也更新
q[now].pop_back();
q[now].push_back((Node2){i, n});
}
int res = 0;
for(int i = 1; i <= N; ++i)
for(int j = 0; j <= min(i, K + 1); ++j)
if(j + N - i == K)
res = max(res, dp[i][j]);
printf("%d\n", res);
return 0;
}