LOJ 4789 「RMI 2020」Peru
考虑到区间可能会有交,这个时候肯定会贪心的让这部分的权值为偏大的一部分。
于是考虑把条件转化为由若干个长度 \(\le k\) 的不交区间覆盖。
那么如果对应的区间是 \([l, r]\),那么贪心的,这个区间选出来的权值就会是 \(\max\limits_{i = l}^r s_i\)。
那么就可以设出 dp。
定义 \(f_i\) 为最后一段右端点是 \(i\) 的最小花费。
转移直接枚举最后一段的左端点即可,就有 \(f_i = \min\limits_{j = i - k + 1}^i\{f_{j - 1} + \max\limits_{p = j}^i s_p\}\)。
那么对于这个 \(\max\limits_{p = j}^i s_p\) 的权值,就肯定需要一个单调栈了。
对于单调栈,假设内部相邻的两个元素是 \(x < y\),那么对于 \(y\) 转移给的值一定就是 \(f_x + s_y\),这是因为 \(f\) 不减。
同时对于单调栈中的栈底 \(x\)(最小的),其贡献一定是 \(f_{i - k} + s_x\),因为区间长度不能超过 \(k\)。
但是同时这个单调栈也会因为区间长度不超过 \(k\) 的限制从栈底弹出下标。
相当于这维护的其实是一个双端队列。
考虑要进行什么操作:
- 末尾加入元素。
- 开头 / 末尾删除元素。
- 查询队列内贡献最小值。
这时有一个方法是用双栈来模拟这个过程。
具体来说,维护两个栈 \(L, R\),其中 \(L\) 的栈顶到 \(L\) 的栈底再到 \(R\) 的栈底再到 \(R\) 的栈顶拼成了完整的双端队列。
那么对于末尾加入元素,就可以直接在 \(R\) 加入栈中,对于前驱分讨一下就可以知道。
假设 \(L, R\) 不会在中途出现空了还需要删的情况,考虑队列最小值怎么处理。
首先对于删除还是直接从对应的 \(L, R\) 中弹出栈顶。
那么能发现这个最小值肯定是 \(L, R\) 的两个栈各自的最小值中的最小值。
那么对于 \(L, R\) 两个栈各自的最小值,发现肯定是栈底到当前的顶的最小值,于是对于每个栈各维护一个从栈顶开始的“前缀”最小值(其实就是栈顶到该位置的最小值)即可。
那么这个值就可以在加入时直接更新,删除时因为新的栈顶的信息已经处理过了,就可以直接用了。
那么这部分的时间复杂度都是 \(\mathcal{O}(n)\) 的。
接下来就考虑怎么处理“\(L, R\) 空时还需要删”的情况。
那么比较直接的想法就是保证 \(|L|, |R| > 0\),当然这个要基于 \(|L| + |R| \ge 2\)。
于是考虑在 \(|L| + |R| \ge 2\) 且 \(|L| = 0\) 或者 \(|R| = 0\) 时直接暴力重构使得 \(|L|, |R| > 0\)。
于是考虑一些重构使得时间复杂度比较优秀,一个比较的直观的想法就是让 \(|L|, |R|\) 都能保持非空的状态尽量久。
那就可以令 \(|L'| = \lfloor\frac{|L| + |R|}{2}\rfloor, |R'| = \lceil\frac{|L| + |R|}{2}\rceil\)。
重构肯定是能做到关于 \(|L| + |R|\) 线性的,直接暴力重新推贡献更新前缀最小值即可。
接下来考虑复杂度分析,考虑令 \(m = \max\{|L|, |R|\}\)。
考虑每次如果是加入操作,那么 \(m'\le m + 1\),如果是删除操作,那么 \(m'\le m\)。
如果是重构,那么花费 \(m\) 的代价,有 \(m' = \lceil\frac{m}{2}\rceil\)。
考虑重构时 \(m - m' = \lfloor\frac{m}{2}\rfloor\) 个元素,此时定义这些元素被删掉了。
此时就可以把 \(m\) 摊到这 \(\lfloor\frac{m}{2}\rfloor\) 个元素上,即每个被删除的元素带有 \(2\) 的代价,且每次重构操作可能会多带有 \(1\) 的代价。
因为一共加入 \(n\) 次,\(m\leftarrow m + 1\) 的次数也不会超过 \(n\) 次,被删除的元素个数就不会超过 \(n\) 个。
且重构的次数也是 \(\mathcal{O}(n)\) 的。
所以最后重构的复杂度也是 \(\mathcal{O}(n)\) 的。
于是最后的复杂度就是 \(\mathcal{O}(n)\)。
代码写的有点丑。
#include<bits/stdc++.h>
using ll = long long;
constexpr ll inf = 1e18;
constexpr ll mod = 1e9 + 7;
constexpr int maxn = 2.5e6 + 10;
ll f[maxn], s[maxn];
struct que {
// top2 >= top1
int stk1[maxn], top1; ll val1[maxn], mn1[maxn];
int stk2[maxn], top2; ll val2[maxn], mn2[maxn];
inline que() {
top1 = top2 = 0;
val1[0] = val2[0] = mn1[0] = mn2[0] = inf;
}
inline void update() {
if (top1 && top2) return ;
if (top1 + top2 == 0) return ;
if (top1 + top2 == 1) {
int x = top1 ? stk1[top1] : stk2[top2];
top1 = 0, top2 = 1, stk2[1] = x;
val2[1] = mn2[1] = inf;
return ;
}
if (! top1) {
top1 = top2 / 2, top2 -= top1;
for (int i = 1; i <= top1; i++) {
stk1[top1 - i + 1] = stk2[i];
}
for (int i = 1; i <= top2; i++) {
stk2[i] = stk2[i + top1];
}
} else {
top2 = (top1 + 1) / 2, top1 -= top2;
for (int i = 1; i <= top2; i++) {
stk2[top2 - i + 1] = stk1[i];
}
for (int i = 1; i <= top1; i++) {
stk1[i] = stk1[i + top2];
}
}
val1[top1] = inf;
for (int i = 1, x, y; i < top1; i++) {
x = stk1[i], y = stk1[i + 1];
val1[i] = s[x] + f[y];
}
val2[1] = s[stk2[1]] + f[stk1[1]];
for (int i = 2, x, y; i <= top2; i++) {
x = stk2[i], y = stk2[i - 1];
val2[i] = s[x] + f[y];
}
for (int i = 1; i <= top1; i++) {
mn1[i] = std::min(val1[i], mn1[i - 1]);
}
for (int i = 1; i <= top2; i++) {
mn2[i] = std::min(val2[i], mn2[i - 1]);
}
}
inline void push_back(int x) {
top2++;
stk2[top2] = x, val2[top2] = top2 > 1 ? (s[x] + f[stk2[top2 - 1]]) : (ll)inf;
mn2[top2] = std::min(val2[top2], mn2[top2 - 1]);
update();
}
inline void pop_back() {
top2--;
update();
}
inline void pop_front() {
if (top1) {
top1--;
if (top1) {
val1[top1] = inf, mn1[top1] = mn1[top1 - 1];
}
} else {
top2--;
}
update();
}
inline bool empty() {
return top2 == 0;
}
inline int front() {
return top1 ? stk1[top1] : stk2[1];
}
inline int back() {
return stk2[top2];
}
inline ll query() {
return std::min(mn1[top1], mn2[top2]);
}
} Q;
int solve(int n, int k, int* s_){
ll ans = 0;
for (int i = 0; i < n; i++) {
s[i] = s_[i];
if (! Q.empty() && i - Q.front() >= k) Q.pop_front();
while (! Q.empty() && s[i] > s[Q.back()]) Q.pop_back();
Q.push_back(i);
f[i] = std::min(Q.query(), s[Q.front()] + (i - k >= 0 ? f[i - k] : 0));
ans = (23ll * ans + f[i]) % mod;
}
for (int i = 1; i < n; i++) {
assert(f[i] >= f[i - 1]);
}
return (int)ans;
}
浙公网安备 33010602011771号