单调队列专题

对于单调队列,我们这样子来定义:
1、维护区间最值
2、去除冗杂状态 如上题,区间中的两个元素a[i],a[j](假设现在再求最大值) 若 j>i且a[j]>=a[i] ,a[j]比a[i]还大而且还在后面(目前a[j]留在队列肯定比a[i]有用,因为你是往后推, 认真想! 重点)
3、保持队列单调,最大值是单调递减序列,最小值反之
4、最优选择在队首
大致过程:
1、维护队首(对于上题就是如果你已经是当前的m个之前那你就可以被删了,head++)
2、在队尾插入(每插入一个就要从队尾开始往前去除冗杂状态)
当然还有不少变形,不过大致就是这样,理解好遇到变通你也不会怕了。

为了让读者更明白一点,我举个简单的例子。
假设数列为:8,7,12,5,16,9,17,2,4,6.N=10,k=3.
那么我们构造一个长度为3的单调递减队列:
首先,那8和它的位置0放入队列中,我们用(8,0)表示,每一步插入元素时队列中的元素如下:
0:插入8,队列为:(8,0)
1:插入7,队列为:(8,0),(7,1)
2:插入12,队列为:(12,2)
3:插入5,队列为:(12,2),(5,3)
4:插入16,队列为:(16,4)
5:插入9,队列为:(16,4),(9,5)
。。。。依此类推
那么f(i)就是第i步时队列当中的首元素:8,8,12,12,16,16,。。。
同理,最小值也可以用单调队列来做。
如果你看懂了,那你就会发现,单调队列的时间复杂度是O(N),因为每个数只会进队和出队一次,所以这个算法的效率还是很高的。
注意:建议直接用数组模拟单调队列,因为系统自带容器不方便而且不易调试,同时,每个数只会进去一次,所以,数组绝对不会爆,空间也是S(N),优于堆或线段树等数据结构。

单调队列的操作
编辑
举例
不妨用一个问题来说明单调队列的作用和操作:
不断地向缓存数组里读入元素,也不时地去掉最老的元素,不定期的询问当前缓存数组里的最小的元素。
最直接的方法:普通队列实现缓存数组。
进队出队都是O(1),一次查询需要遍历当前队列的所有元素,故O(n)。
用堆实现缓存数组
堆顶始终是最小元素,故查询是O(1)。
而进队出队,都要调整堆,是O(log(n))。
RMQ的方法
RMQ即Range Maximum(Minimum) Query,用来求某个区间内的最大值或最小值。使用线段树或稀疏表是O(log(n))级的。对于这类问题这两种方法也搞得定,但是没有单调队列快。
单调队列的舞台
由于单调队列的队头每次一定最小值,故查询为O(1)。
进队出队稍微复杂点:
进队时,将进队的元素为e,从队尾往前扫描,直到找到一个不大于e的元素d,将e放在d之后,舍弃e之后的所有元素;如果没有找到这样一个d,则将e放在队头(此时队列里只有这一个元素)。出队时,将出队的元素为e,从队头向后扫描,直到找到一个元素f比e后进队,舍弃f之前所有的。(实际操作中,由于是按序逐个出队,所以每次只需要出队只需要比较队头)。每个元素最多进队一次,出队一次,摊排分析下来仍然是 O(1)。上面的话可能还是没能讲出单调队列的核心:队列并不实际存在的,实际存在的是具有单调性的子序列。对这个子序列按心中的队列进行操作,譬如在进队时丢弃的元素,虽然它不存在于这个子序列里,但是还是认为他存在于队列里。另外,进队的顺序和出队的顺序并不一定相同,因为这个队列本身是隐含存在的,可以在进队时看成一个队列,出队时看成另一个队列,只要出队的元素在队列中就行。可以想象成一个队列只有头和身,另一个队列只有身和尾,而这身是共用的。
在OI赛场上,大多数题目为单调队列力所不能及的,取而代之的是单调队列基础上改进的斜率优化,单调栈等,因为其限制条件,故潜力不大。但需要掌握,因为有许多算法建立在其基础上。
例如斜率优化即为f[i] = min/max{f[j] + g[i] * g[j]},和单调队列尤为相似。
单调栈即为单调队列的“栈”版。
这两种复杂度也是O(n)的。

你可能感兴趣的:(单调队列专题)