所谓单调队列,就是一个保持着某种性质的队列,通常是队列从队头到队尾,维护一种递增递减的关系。
这种队列通常用来解决一些连续区间的最值问题。
这种队列的入队要保证符合当前的性质,例如一个递增的单调序列(从左到右是从头到尾):7,9,10,11
这时,入队时要保证是递增的:例如12这个元素可以入队,而10这个元素不行。
这时我们看队头,这就是整个队列中最小的元素。所以,递增的队列可以维护最小值;递减的队列可以维护最大值。
当然,实现一个单调队列需要看题目要求,通常需要对队头和队尾进行更新。
例如这道题:
P1886 滑动窗口
题目描述
现在有一堆数字共N个数字(N<=10^6),以及一个大小为k的窗口。现在这个从左边开始向右滑动,每次滑动一个单位,求出每次滑动后窗口中的最大值和最小值。
这道题可以使用单调队列来做。
在滑动窗口的时候,改变只有窗口序列中的第一个元素和最后一个元素,所以我们可以把一开始的序列同样的放到两个单调队列中,一个递增,一个递减,分别维护最小值和最大值。
用求最小值(递增单调队列)做例子,我们对每个元素运行这样一个判断:
1.如果当前元素<队尾,那么删除队尾。不停执行该操作,直到队为空且没有可以删除的队尾为止。
2.在队尾加上当前元素。
3.如果队头离开了窗口的范围,那么删除队头。
求最大值的操作类似。
根据上题,我们可以总结出单调队列一般有这样的步骤:
1.如果当前元素小于或者大于(递增或者递减)队尾,那么删除队尾。不停执行该操作,直到队为空且没有可以删除的队尾为止。
2.在队尾加上当前元素。
3.如果队头离开了题目的范围,那么删除队头。
P1440 求m区间内的最小值
题目描述
一个含有n项的数列(n<=2000000),求出每一项前的m个数到它这个区间内的最小值。若前面的数不足m项则从第1个数开始,若前面没有数则输出0。
这道题依然用单调队列做。注意,这道题的区间指的是每一项的前m个数,不包括这一项。
所以,我们维护一个递增的单调队列,长度最长为m,步骤类似上文所讲。
代码:
#include<iostream> #include<vector> #include<cstdio> #include<queue> #include<map> #include<cstdlib> #include<cmath> #include<algorithm> #include<set> #include<cstring> using namespace std; typedef long long ll; const ll INF=99999999; const int MaxN = 2000010; int a[MaxN]; int que[MaxN][2]; int n,m,head = 1,tail; int main() { //freopen("testdata.in","r",stdin); //freopen("testdata.out","w",stdout); scanf("%d%d",&n,&m); for(int i = 1; i <= n; i++){ scanf("%d",&a[i]); } printf("0 "); que[++tail][0] = a[1]; que[tail][1] = 1; for(int i = 2; i <= n; i++){ while(head <= tail && a[i-1] < que[tail][0]){ tail--; } que[++tail][0] = a[i-1]; que[tail][1] = i-1; while(head <= tail &&que[head][1] < i-m) head++; printf("%d ",que[head][0]); } return 0; }