单调队列,顾名思义,就是一个元素具有单调性的队列,{1,2,5}这是一个单调增的队列,{6,4,1}这是一个单调减的队列。
其用途我们通过一道例题来说明
洛谷P1866 滑动窗口
现在有一堆数字共N个数字(N<=10^6),以及一个大小为k的窗口。现在这个从左边开始向右滑动,每次滑动一个单位,求出每次滑动后窗口中的最大值和最小值。
例如
题意是有一个长度为N的序列,还有一个长度为k的区间,区间每次向右滑动一位,求每次滑动过后的最大值和最小值。
这道题可以用线段树或ST表来解决,这里仅仅说单调队列。
如果我们维护一个队列,保持队首是要查找的值,那么每次滑动的时候,我们输出队首即可。
现在问题是,我们怎么样维护这样一个合法的队首,我们就应用了单调队列。
这里以最大值为例,试想,如果碰到一个新的元素num[j](i<j),如果num[i]>num[j],那么num[i]和num[j]都是有用的,
因为如果窗口往后滑,num[i]滑出了区间,弹出队列,那么num[j]可能会成为那个合法的队首,所以num[i]和num[j],
我们都要记下来,再如果num[i]<=num[j](i<j),那么num[i]永远也不会成为将要查询区间的合法元素,所以直接弹掉
即可,明确了这一点剩下只是一些简单的操作。
在队列中记下每个元素在原序列中的位置,如果这个元素不在查询序列中了就弹掉。
如果碰到num[i]<=num[j](i<j),就弹掉队尾破坏单调性的元素,知道满足单调性了,就将新的元素加入。
如果碰到num[i]>num[j] (i<j),就把num[j]加入队尾,因为没有破坏单调性。
最小值的查询相反即可。
P1866 代码(维护一个单调增队列查询最小值,再维护一个单调减队列查询最大值)
1 #include<cstdio> 2 #include<iostream> 3 #include<cstring> 4 #include<algorithm> 5 #define maxn 1000005 6 7 using namespace std; 8 9 struct node 10 { 11 int data,pos; 12 }; 13 node q[maxn]; 14 int num[maxn],n,k; 15 16 int main() 17 { 18 scanf("%d%d",&n,&k); 19 for(int i=1;i<=n;i++) 20 scanf("%d",&num[i]); 21 int head=1,tail=0; 22 for(int i=1;i<=n;i++) 23 { 24 while(head<=tail&&q[head].pos+k<=i) head++; 25 while(head<=tail&&q[tail].data>=num[i]) tail--; 26 q[++tail].data=num[i]; q[tail].pos=i; 27 if(i>=k) printf("%d ",q[head].data); 28 } 29 printf("\n"); 30 head=1; tail=0; 31 for(int i=1;i<=n;i++) 32 { 33 while(head<=tail&&q[head].pos+k<=i) head++; 34 while(head<=tail&&q[tail].data<=num[i]) tail--; 35 q[++tail].data=num[i]; q[tail].pos=i; 36 if(i>=k) printf("%d ",q[head].data); 37 } 38 return 0; 39 }