单调队列/单调栈优化
引入
单调队列主要用于维护两端指针单调不减的区间最值,而单调栈则主要用于维护前/后第一个大于/小于当前值的数。
注意
- 求最小值要维护 单调递增/不减 的单调队列/单调栈,反之亦然。
- 维护单调递增/递减比较时用 小于等于/大于等于,维护单调不减/不增比较时用 小于/大于。
单调队列优化具体步骤
- 加入所需元素:向单调队列重复加入元素直到当前元素达到所求区间的右边界,这样就能保证所需元素都在单调队列中。
- 弹出越界队首:单调队列本质上是维护的是所有已插入元素的最值,但我们想要的往往是一个区间最值。于是我们弹出在左边界外的元素,以保证单调队列中的元素都在所求区间中。
- 获取最值:直接取队首作为答案即可。
单调栈优化具体步骤
- 弹出非法栈顶:通过比较当前元素与栈顶的大小,弹出不满足单调栈性质的栈顶。以单调递增的栈(即栈顶最大,维护最小值)为例,将所有大于等于当前元素的栈内元素全部弹出。
- 加入当前元素:将当前元素入栈即可。
单调队列优化多重背包
问题描述
你有
不了解背包 DP 的请先阅读 背包 DP。设
时间复杂度
考虑优化
设
这样就转化为一个经典的单调队列优化形式了。
在实现的时候,我们需要先枚举 f[last][q.front() * w[i] + y] - q.front() * v[i]
获取对应的
参考代码
#include <array>
#include <deque>
#include <iostream>
constexpr int MAXV = 4e4 + 10;
constexpr int MAXN = 1e2 + 10;
using namespace std;
int n, W, last = 0, now = 1;
array<int, MAXN> v, w, k;
array<array<int, MAXV>, 2> f;
deque<int> q;
int main() {
ios::sync_with_stdio(false);
cin >> n >> W;
for (int i = 1; i <= n; i++) {
cin >> v[i] >> w[i] >> k[i];
}
for (int i = 1; i <= n; i++) {
for (int y = 0; y < w[i]; y++) {
// 清空队列
deque<int>().swap(q);
for (int x = 0; x * w[i] + y <= W; x++) {
// 弹出不在范围的元素
while (!q.empty() && q.front() < x - k[i]) {
q.pop_front();
}
// 保证队列单调
while (!q.empty() && f[last][q.back() * w[i] + y] - q.back() * v[i] <
f[last][x * w[i] + y] - x * v[i]) {
q.pop_back();
}
q.push_back(x);
f[now][x * w[i] + y] =
f[last][q.front() * w[i] + y] - q.front() * v[i] + x * v[i];
}
}
swap(last, now);
}
cout << f[last][W] << endl;
return 0;
}
习题
例题 CF372C Watching Fireworks is Fun
题目大意:城镇中有
初始你可在任意位置,你每个单位时间可以移动不大于
设
写出状态转移方程:
尝试变形:
由于
如果确定了
最后,式子变为:
接下来考虑单调队列优化。由于最终式子中的
总的时间复杂度为
参考代码
#include <algorithm>
#include <cstring>
#include <iostream>
using namespace std;
using ll = long long;
constexpr int MAXN = 150000 + 10;
constexpr int MAXM = 300 + 10;
ll f[2][MAXN];
ll a[MAXM], b[MAXM], t[MAXM];
int n, m, d;
int que[MAXN];
int fl = 1;
void init() {
memset(f, 207, sizeof(f));
memset(que, 0, sizeof(que));
for (int i = 1; i <= n; i++) f[0][i] = 0;
fl = 1;
}
void dp() {
init();
for (int i = 1; i <= m; i++) {
int l = 1, r = 0, k = 1;
for (int j = 1; j <= n;
j++) { // 在这里使用了单调队列的优化,推式子详见上面
for (; k <= min(1ll * n, j + d * (t[i] - t[i - 1])); k++) {
while (l <= r && f[fl ^ 1][que[r]] <= f[fl ^ 1][k]) r--;
que[++r] = k;
}
while (l <= r && que[l] < max(1ll, j - d * (t[i] - t[i - 1]))) l++;
f[fl][j] = f[fl ^ 1][que[l]] - abs(a[i] - j) + b[i];
}
fl ^= 1;
}
}
int main() {
cin >> n >> m >> d;
for (int i = 1; i <= m; i++) cin >> a[i] >> b[i] >> t[i];
// then dp
dp();
ll ans = -1e18;
for (int i = 1; i <= n; i++) ans = max(ans, f[fl ^ 1][i]);
cout << ans << endl;
return 0;
}
创建日期: 2019年3月15日