• 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
    ", res);
    	return 0;
    }
    
  • 相关阅读:
    linux基本命令之grep
    Linux在线练习网站
    MySql分表分库思路
    SqlServer触发器
    SqlServer存储过程
    Spring常用注解总结
    SpringMVC的HelloWorld
    XML基础和Web基本概念(JavaWeb片段一)
    红黑树-结点的删除(C语言实现)
    红黑树-结点的插入
  • 原文地址:https://www.cnblogs.com/hnfms-jerry/p/solution-Lifeguards_P.html
Copyright © 2020-2023  润新知