单调队列优化 dp
1. 概念
单调队列优化的本质是借助单调性,及时排除不可能的决策,保持候选集合的秩序性。
2. 例题
P1714 切蛋糕
题目大意:
给定一个序列,找出长度不超过
思路:
要求区间和,首先求出前缀和,然后考虑朴素 dp,不难想到用
所以朴素代码很快就能出炉:
#include <cstring>
#include <iostream>
using namespace std;
const int N = 500010;
int n, m;
int sum[N], dp[N];
int ans;
int main() {
scanf("%d%d", &n, &m);
int x;
for(int i = 1; i <= n; i++) {
scanf("%d", &x);
sum[i] = sum[i - 1] + x;
}
memset(dp, -0x3f, sizeof dp);
for(int i = 1; i <= n; i++) {
for(int j = max(i - m + 1, 0); j <= i; j++)
dp[i] = max(dp[i], sum[i] - sum[j - 1]);
ans = max(ans, dp[i]);
}
printf("%d\n", ans);
return 0;
}
时间复杂度为
顺便提一句动态规划的优化思路:
一个原则:
在朴素代码上做等价变形。
三个方向:
- 优化状态设计,阶段和状态转移方程;
- 若有状态被多次计算或调用,则考虑加上记忆化搜索;
- 若有多层循环,考虑将与外层循环相关的变量看作定值,及时排除内层循环中的不可能决策或利用数据结构等方法优化找最值的过程,从而优化掉内层循环。
很显然,这道题我们选择方向
容易发现,在外层循环到
所以,内层循环的作用其实是寻找
是不是和滑动窗口如出一辙?
所以我们可以用单调队列来优化这个找最小值的过程。
#include <cstring>
#include <iostream>
using namespace std;
const int N = 500010;
int n, m;
int sum[N];
int q[N], hh, tt = -1;
int ans = -0x3f3f3f3f;
int main() {
scanf("%d%d", &n, &m);
int x;
for(int i = 1; i <= n; i++) {
scanf("%d", &x);
sum[i] = sum[i - 1] + x;
}
for(int i = 1; i <= n; i++) {
if(hh <= tt && i - m - 1 >= q[hh]) hh++;
while(hh <= tt && sum[i - 1] <= sum[q[tt]]) tt--;
q[++tt] = i - 1;
ans = max(ans, sum[i] - sum[q[hh]]);
}
printf("%d\n", ans);
return 0;
}
总结:
在状态转移方程中当前状态的所有值可以从上一个状态的某个连续的段的值得到,要对这个连续的段进行 RMQ 操作,相邻状态的段的左右区间满足非降的关系时,就可以使用单调队列优化 dp。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战