单调队列DP

单调队列不是传统意义上的FIFO的结构,主要用于维护一段固定长度区间的属性。

  1. 基础模版

滑动窗口:求对于已知序列每个元素的一段固定长度为k的区间 中的最大值。单调队列存放遍历到的元素对应的窗口中的 单调递减顺序排列 的元素 的下标,单调队列的首元素即为所求值的下标。每个元素最多进队和出队各一次,所以时间复杂度O(n)。

int hh=0,tt=-1;

//如果下面遍历从1开始,这两个初始值也不需要+1,因为它是相对位置。

for(int i=0;i<n;i++){

if(hh<=tt && q[hh]<i-k+1) hh++;

//首先窗口移动一格,队首元素弹出,注意是i-k+1

while(hh<=tt && q[tt]<=a[i]) --tt;

//如果遍历到的元素比队尾元素大就弹出队尾,给它腾出位使得窗口序列严格单调,注意取等

q[++tt]=a[i];

//最后,腾出来就可以入队了

if(i>k-1) cout<<a[q[hh]]<<” ”;

//最开始的时候坐标小于队列长度,说明队列没满,即区间长度不满足k,不输出

}

  1. 单调队列优化DP

优化都是在朴素dp的基础上进行的,所以一定要先清楚朴素dp的思路

1.最大子序和:

求一个数组的长度不超m的子区间(连续)和最大值。

暴力思路是前缀和优化枚举区间,dp思路是枚举区间右端点。

状态表示:dp[i]是以a[i]为右端点的长度不超过m的连续子区间和

状态属性:区间和最大值

状态计算:f[i] = max{sum[i]-sum[j]} (1<= i - j <=m)

= sum[i]-min{sum[j]} (i-m<= j <=i-1)

这里有个数学常识,有些人没注意到就会影响式子理解:所求的f值是与i相关的,是关于i的状态的计算,这时只和i相关的值算作常量,可以提出来。

观察变形式子发现,从前向后维护一个长度不超m的区间最小值,用单调队列维护区间左端点,最终答案是所有f[i]的最大值。

int hh=0,tt=0;

for(int i=1;i<=n;i++){

if(hh<=tt && i-q[hh]>m) hh++;

//这里注意范围,理由见下方文字

res=max(res,s[i]-s[q[hh]]);

while(hh<=tt && s[q[tt]]>=s[i]) --tt;

q[++tt]=i;

}

注意tt=-1是适用于原数组元素的模版,而tt=0是适用于前缀和的模版,相当于push s[0]下标。因为以a[i]结尾的子串左端点范围是[i-m+1, i],求区间和公式为s[r]-s[l-1],则窗口维护的l∈[i-m, i-1],因此q[hh]最小取i-m,并且窗口内取不到i。

  1. 修剪草坪

求正整数数组一个子序列(非连续)使得序列和最大。其中序列中的元素在原数组连续相邻超过m个则他们的和变为0→子序列长度不超过m。

y总方法看题解区第二篇,这里用逆向求解更好理解:

题意转化为原数组中每连续k+1个数里就必须去除一个不选,使去除的元素之和(损失值)最小,从而使答案即 总和-损失值 最大。窗口维护元素是去除的元素(单增),队首为最小元素。(剩下的过程详见5)

  1. 旅行问题(待补)

一个环n个点,第i个点可以给车加p[i]的油,从i到i+1要耗d[i]的油。车从任意一个点出发,过程中油必须是非负,判断能否顺时针或逆时针回到起点。

环形dp转线性,从任意两点间(不妨从n~1号点)将环断开,将此链复制一份接到原链后面。则从任意点出发回到起点的路径,对应链上从该点开始的长度为 n+1 的区间,这个区间用滑动窗口维护,

由题意得到达i点的条件是此前剩余油+该点获得油>=到该点的消耗油

  1. 理想的正方形(待补)

求a*b矩阵中边长为n的子正方形中的所有元素的 最大值-最小值 的最小值。

  1. 烽火传递

每连续m个数就必须选至少1个,求总贡献最小值。

状态表示:f[i]是选择a[i]且[1~i-1]合法的方案

状态属性:1~i的最小总贡献

状态计算:f[i] = min{w[i]+f[j]} (0<=i-j-1<=m-1)

= w[i]+min{f[j]} (1<=i-j<=m)

注意我们可以确定答案的范围一定在[n-m+1~n],所以y总输出答案的方法是遍历f[n-m+1]到f[n]取最小。其实可以利用滑动窗口的性质输出答案:第n次滑动时j的范围是[n-m~n-1],要想让j的范围为答案的范围,就再滑一位,由于队首元素为该区间的最小值,所以它就是答案。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值