单调队列
单调队列引入:
首先看一个例题:
给你一个长度为N的数组,一个长为K的滑动框从最左端滑到最右端,你只可以看到框内的K个数,每次框向右移动一格。请你找出框在各位置的最小值。
样例输入:
8 3
1 3 -1 -3 5 3 6 7
样例输出:
-1 -3 -3 -3 3 3
代码实现(已经修改,谢谢HLongSh大佬指正):
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
int n,m,i,j,k,l,h,t;
int a[100000],q[100000],time[1000001];/*a中存储的就是数组中元素的值,q中存储的是单调队列该位置所对应的数组中元素的值,time是保存单调队列该位置所对应的数组中元素进队的时间*/
int main()
{
cin>>n>>k;
for (i=1;i<=n;i++) cin>>a[i];
h=1;
for (i=1;i<=n;i++)
{
while (i-time[h]>=k&&(h<t)) h++;//队首元素超出K区间边界,需要剔除。
while (t>=h&&t>0&&t>=l&&q[t]>a[i]) t--;//从队尾向前扫描,直到找到一个比a[i]小的。此时相当于把后面的剔除了。因为已经可以确定那些值不会是ans。
t++;
q[t]=a[i];
time[t]=i;
if (i>=k) cout<<q[h]<<' ';
}
}
这个例题充分说明了单调队列是干什么的?来查找不断移动的K区间内的最小(大)值(动态规划中应用广泛)。
单调队列的维护:
1.这里采用了最直接的方法:普通队列实现缓存数组。
进队出队都是O(1),一次查询需要遍历当前队列的所有元素,故O(n)。
2.还可以用堆实现缓存数组
堆顶始终是最小元素,故查询是O(1)。(对q数组用堆来维护)
而进队出队,都要调整堆,是O(log(n))。
单调队列的过程:
查询:单调队列的队头一定最小值,所以查询为O(1)。
进队:进队时,将进队的元素为a[i],从队尾往前扫描,直到找到一个不大于a[i]的元素f[t],把a[i]放在f[t]之后,舍弃f[t]之后的所有元素;如果没有找到,则把a[i]放在队头(该值当前最小)。
出队:出队时,将出队的元素为q[h],从队头向后扫描,直到找到一个‘未老’的元素(即在给定的范围当中),舍弃该元素之前所有的。
每个元素最多进队一次,出队一次,均摊下来仍然是 O(1)。
单调队列实际上是具有单调性的子序列,但这并不是一个真正意义上的队列,在进队和出队时对某段进行反复操作。但是却具有队列的特性。
单调队列和二分,线段树一样是用来优化的一种数据结构。在动态规划中应用较广泛。