单调队列是一种特殊的双端队列,其满足单调性,即内部元素单调递增或单调递减。单调队列可以用数组模拟,也可以用$STL$中的$deque$实现。
---------------------------
例题 最大子序和
给定一个长度为$N$的整数序列,从中找出一段长度不超过$M$的连续子序列,使得子序列中所有数的和最大。
$N,Mleq 3*10^5$。
区间和可以转化成“两个前缀和相减”求解。所以问题转化为“找出两个位置$x,y$,使得$S[y]-S[x]$最大且$y-xleq M$。”
固定右端点$i$,此时要找到一个左端点$j$,$i-mleq jleq i-1$且$S[j]$最小。
比较一下任意两个位置$j$和$k$。如果$k<j<i$并且$S[k]geq S[j]$,那么对于大于等于$i$的右端点,$k$永远不会成为最优选择。因为$S[k]geq S[j]$且$j$更靠近$i$,,即$j$的生存能力更强,长度更不容易超过$M$。
以上告诉我们,最优选择的策略集合一定是一个下标递增,对应前缀和$S$也递增的一个序列。
利用单调队列维护,有3个步骤:
1.判断队头决策与$i$的距离是否超过$M$,若是则出队。
2.此时队头就是最优选择。
3.不断删除队尾决策直到队尾值小于$S[i]$。然后将$i$作为新的决策入队。
代码:
int l=1,r=1; q[1]=0; for (int i=1;i<=n;i++) { while(l<=r&&q[l]<i-m) l++; ans=max(ans,sum[i]-sum[q[l]]); while(l<=r&&sum[q[r]]>=sum[i]) r--; q[++r]=i; }
单调队列优化线性DP
形如$f[i]=max{f[j]}+value$的DP方程,我们可以尝试用单调队列优化。
例题 [USACO11OPEN]Mowing the Lawn G
----------------------------
状态:设$f[i][0]$表示以$i$结尾且$i$这个数不选所得的最大效率值;$f[i]i1]$表示选这个数的最大效率值。
不难得出状态转移方程:
$f[i][0]=max(f[i-1][0],f[i-1][1])$。
$f[i][1]=max(f[j][0]+sum[i]-sum[j]) (i-kleq j<i)$。
可以转化成$f[i][1]=max(f[j][0]-sum[j])+sum[i] (i-kleq j<i)$。可以用单调队列优化。
注意开$long long$。
代码:
#include<bits/stdc++.h> #define int long long using namespace std; int f[100005][2],n,k; int q[100005],l=1,r=1; int sum[100005],a[100005]; inline int read() { int x=0,f=1;char ch=getchar(); while(!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();} while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();} return x*f; } signed main() { n=read(),k=read(); for (int i=1;i<=n;i++) { a[i]=read(); sum[i]=sum[i-1]+a[i]; } int l=1,r=1; for (int i=1;i<=n;i++) { f[i][0]=max(f[i-1][0],f[i-1][1]); while(q[l]<i-k&&l<=r) l++; f[i][1]=f[q[l]][0]-sum[q[l]]+sum[i]; while(f[i][0]-sum[i]>f[q[r]][0]-sum[q[r]]&&l<=r) r--; q[++r]=i; } printf("%ld",max(f[n][0],f[n][1])); return 0; }