LG-P4182 [USACO18JAN]Lifeguards P Solution
目录更好的阅读体验戳此进入
解法本身题解区已经写的较为清楚了,这里主要对大多数题解都涉及到但均未证明的一个贪心策略进行感性证明,故前面的推导过程写的较为简略。
题面
给定 $ n $ 个左闭右开的区间,在范围 $ [0, 10^{9}] $ 内,最大化删去其中 $ k $ 个区间后剩下区间并起来的长度,求最大值。
$ 1 \le n \le 10^5, 1 \le k \le \min(100, n) $。
Solution
首先不难想到对于区间 $ [l_1, r_1), [l_2, r_2) $ 且 $ l_1 \le l_2 \land r_2 \le r_1 $ 那么删去 $ [l_2, r_2] $ 答案一定不变,于是可以直接全部删去,并将 $ k \leftarrow k - 1 $。
然后 DP 显然,也很好想到一个状态 $ dp(i, k) $ 表示考虑前 $ i $ 个区间,删去其中的 $ k $ 个的最优值,然后发现这玩意没有固定具体怎么删没法转移,然后就想到在原来的状态基础上钦定必须选择第 $ i $ 个区间,然后需要把区间优先按左端点,再按右端点排序,这样我们就会发现最开始去掉那些区间的作用了,去掉之后现在我们剩下的区间排序后一定满足对于 $ l $ 的升序一定也满足 $ r $ 升序,不满足的已经被我们删掉了。于是此时转移比较显然:$ dp(i, k) = \max{ dp(j, k - (i - j - 1)) + r_i - \max(l_i, r_j) }, j \in [i - k - 1, i - 1] $。这玩意复杂度显然是 $ O(nk^2) $ 的,显然不正确。
考虑优化,状态没什么可优化的,于是考虑优化转移,我们尝试提出来与转移时的 $ j $ 无关的项,令 $ \xi = i - k - 1 $,于是有 $ dp(i, k) = \max{ dp(j, j - \xi) + r_i - \max(l_i, r_j) }, j \in [i - k - 1, i - 1] $。也就是说我们要在 $ \xi = i - k - 1 $ 的前提下,换句话说就是以前的 $ dp $ 中前后索引的差为 $ \xi $ 的值中找到后面式子的最大值进行转移,这个东西简单分类讨论一下就是如果 $ l_i \ge r_j $(或者理解为区间无交)那么就是要最大化 $ dp(j, j - \xi) $。反之需要最大化 $ dp(j, j - \xi) - r_j $。
所以我们直接对前者维护一个最大值(因为我们的区间之间使有序的,所以如果对于之前的区间已经无交了,对于现在的一定也无交),后者则维护一个单调队列单调栈单调双端队列,每次处理时先把对应单调双端队列队头中所有不合法的,也就是不交的弹出然后更新不交区间里面的最大值。然后用交的和不交的里的最大值更新当前 $ dp $,每次处理完当前的 $ dp $ 就把当前的丢到对应的单调双端队列里,这时我们会按照双端队列直接把队尾不优于当前的直接丢掉而不是保留下来等待不交时更新不交的最大值,这个的原因我想了很久,感性地证明了一下,如下:
如果你仔细想一下就会发现这个算法有一点问题,首先我们在最初的找单调双端队列里面的队头不合法的值的时候,我们时按照 $ val $ 的大小,也就是 $ dp(j, j - \xi) - r_j $ 的大小维护的单调双端队列,但是我们更新不交最大值的时候需要的是与其不交区间里的最大值(废话),难道就不会存在一种情况,即队头的区间 $ val $ 更大,但是区间靠右,然而队头之后某个区间 $ val $ 较小却区间靠左,也就是存在一种情况单调优先队列的后面已经存在了不交的区间但是却被队头的相交区间阻挡了,这样的话我们难道不会少更新一些值吗?还有就是在把当前的 $ dp $ 压入单调双端队列的时候,我们会把队尾不优于当前值的直接踢掉,这个时候却没有考虑到在后面更新时这些区间可能会从相交变成不交,从而可能性地去更新不交的最大值,但是现在这被我们丢掉了,难道不会使答案更劣吗?
这几个问题困扰了我很久,完全没有头绪,直到发现了一个性质我才大概能感性理解这个贪心的正确性。
显然当我们转移的时候,如果转移的这个 $ j $ 是与 $ i $ 交的,那么最优的 $ j $ 一定是 $ l_j $ 最小的 $ j $,当然经过我们的处理之后同时也是 $ r_j $ 最小的。这个不难理解,因为我们处理过包含的区间,所以 $ i, j $ 相交那么最终这两段组成的区间一定是 $ [l_j, r_i) $,那么 $ l_j $ 尽量小一定更优,所以 $ j + 1, j + 2, \cdots, i - 1 $ 的区间都是没必要选的,选了也不会有任何贡献。
此时我们再回到刚才的考虑,我们的单调优先队列维护的是最大值,而最大值一定是 $ l $ 和 $ r $ 最小的,那么我们实际上也可以感性地认为更左的区间在单调双端队列里也更靠前,如果这个成立那么我们刚才的所有问题也就很显然是不需要考虑得了,这样的话我们弹出的队头一定是最先变得不交的,被丢掉的区间也一定是更劣的。
于是这个贪心(应该)是正确的。
当然上面这一大段证明都完全是我口糊的,不保证正确性,也不够理性和严谨,期待一个更严谨的证明。
Code
#define _USE_MATH_DEFINES
#include <bits/extc++.h>
#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW(arr) void* Edge::operator new(size_t){static Edge* P = arr; return P++;}
using namespace std;
using namespace __gnu_pbds;
mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}
typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;
template< typename T = int >
inline T read(void);
struct Line{
int l, r;
friend const bool operator < (const Line &a, const Line &b){if(a.l == b.l)return a.r < b.r; return a.l < b.l;}
}line[110000];
list < Line > _line;
int N, K;
int cnt(0);
struct Status{int idx; int val;};
deque < Status > bst[110000];
int mx[110000];
int dp[110000][110];
int main(){
N = read(), K = read();
for(int i = 1; i <= N; ++i){
int l = read(), r = read();
_line.emplace_back(Line{l, r});
}_line.sort();
for(auto it = next(_line.begin()); it != _line.end();)
if(it->r <= prev(it)->r)it = _line.erase(it), --K; else ++it;
for(auto ln : _line)line[++cnt] = ln;
N = cnt; K = max(0, K);
for(int i = 1; i <= N; ++i){
for(int k = 0; k <= min(i - 1, K); ++k){
int xi = i - k - 1;
while(!bst[xi].empty() && line[bst[xi].front().idx].r < line[i].l){
auto tp = bst[xi].front(); bst[xi].pop_front();
mx[xi] = max(mx[xi], tp.val + line[tp.idx].r);
}
dp[i][k] = max({
dp[i][k],
mx[xi] + line[i].r - line[i].l,
bst[xi].empty() ? -1 : bst[xi].front().val + line[i].r
});
int val = dp[i][k] - line[i].r;
int pos = i - k;
while(!bst[pos].empty() && bst[pos].back().val < val)bst[pos].pop_back();
bst[pos].push_back(Status{i, val});
}
}int ans(-1);
for(int i = N - K; i <= N; ++i)ans = max(ans, dp[i][K - (N - i)]);
printf("%d\n", ans);
fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
return 0;
}
template < typename T >
inline T read(void){
T ret(0);
short flag(1);
char c = getchar();
while(c != '-' && !isdigit(c))c = getchar();
if(c == '-')flag = -1, c = getchar();
while(isdigit(c)){
ret *= 10;
ret += int(c - '0');
c = getchar();
}
ret *= flag;
return ret;
}
UPD
update-2022_11_03 初稿
标签:LG,int,题解,最大值,队列,双端,区间,P4182,dp From: https://www.cnblogs.com/tsawke/p/16945544.html