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)\)

所以我们要开单调队列优化.
这里的单调队列用中的deque来实现(其实手动实现也可).
考虑到如果之前的一个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;
}
posted @ 2018-06-25 15:32  Jerx2y  阅读(313)  评论(0编辑  收藏  举报