简单的单调栈算法


基本算法

简介

单调栈是一种算法, 它可以用一次扫描 (O(n)) 时间求出序列中每个数向左和向右的第一个大于它的数, 也可以用一次扫描 (O(n)) 时间求出序列中每个数向左和向右的第一个小于它的数。

以大于为例:

用单调栈求左边第一个大于的数:

有数列(ig<aig>)

对于一个数 (a_i) 前面的两个数 (a_p,a_q, quad p<q<i), 若 (a_p<a_q), 则对于所有 (jge i)(a_p) 都不可能成为 (a_j) 的答案。

单调栈就是对于每个数维护可能成为其答案的下标集合, 然后从里面快速选出答案。

具体地, 算法流程是:

一、 从左到右扫描数列, 开一个空栈 (stk), 栈顶为 (stk[tp])

二、对于每个数 (a_i)

  1. 在栈不为空且 (stk[tp] le a_i) 的时候使栈不断 (pop)

  2. 停止 (pop) 后, 若栈不为空, (stk[tp]) 就是 (a_i) 左边第一个大于其的数, 否则 (a_i) 左边没有大于其的数。

  3. (a_i) 加入到栈顶

从以上算法可以看出, 每个数 (a_i) 都是会被加入栈里的, 那么在以后的扫描过程中, 让 (a_i) 从栈中弹出的那个数一定就是 (a_i) 右边第一个大于其的数, 若自始至终 (a_i) 没有被弹出, 则 (a_i) 右边没有大于其的数。

至此, 可以用单调栈一次扫描求出每个数左边和右边第一个大于其的数, 对于小于, 只需简单地魔改一下算法就可以了, 用到的理论也是高度类似的

对于基本算法的正确性的证明

需要证明的仅仅是求左边第一个大于的数的正确性, 懂的都懂。

那么对于每个数 (a_i), 在 算法流程 扫描到 (a_i), 执行到 二-2 的时候, 为什么能直接确定答案?

首先可以确定栈中没有不可能成为 当前数的答案 的数 (当然也没有不可能成为以后数的答案的数), 此时选栈顶, 即最靠近当前数的数, 就是答案。

为什么可以确定这个事实? 可以用归纳法证明, 在这里就不证了, 仅给出参考思路:

假设扫描到 (a_i),

  1. 用归纳法证明到步骤 二-1 之前,栈中没有不可能成为 (a_i) 答案的数, 所有可能成为 (a_i) 答案的数都在栈中。
  2. 用归纳法证明到步骤 二-1 之前,从栈底到栈顶是一个单调递减的数列, 这就可以证明执行完步骤 二-1 之后栈顶就是答案

更深一点的应用

可以用归纳法证明, 对于数列 (ig<aig>), 在使用基本算法求每个数的左边的第一个大于其的数时, 对于当前扫描的数 (a_i), 单调栈实际上是维护的数列 (ig<aig>_{1cdots i}) 的后缀最大值(从栈顶到栈底, 就是 (max(a[icdots i])、max(a[i-1cdots i])、cdots、max(a[1cdots i])) 去重后的结果)。

于是在单调栈的过程中, 可以见到整个数列所有子串的最大值, 至于有多少应用嘛……不知道。

应用

1

(sum_{l=1}^nsum_{r=l}^n max(a_{lcdots r}))

根据上面的 更深一点的应用 单调栈 (O(n)) 搞, 很不错。

还可以枚举所有 (a_i), 算出 max 是 (a_i) 的区间有多少个, 这个也可以用单调栈求出来。

1的升级版

(f(X,Y) = sum_{l=X}^Ysum_{r=l}^Y max(a_{lcdots r}))

(Q) 次询问不同的 (f(X,Y))

要求 (O((n+Q)log n))

似乎不太可做, 待补。

2

给定数列 (a) , 求 (max{a[Lcdots R]}*(a_R-a_L)) 的最大值。

枚举 (max{a[Lcdots R]}), 再讨论一下, 就做完了。(用 st表 可以做到 (O(nlog n))

如果把 (a_R-a_L) 换成 (R-L+1), 可以做到 (O(n))

3

给定数列 (a), 求有几对 ((x,y)) 满足:
(max(a[x+1cdots y-1]) le min(a[x],a[y]))

顺便可以求出具体方案。

单调栈求左边第一个大于的数的过程中, 被一个数弹掉的所有数以及此数的答案, 就是所有可以使这个数作为 (y)(x)

这样的对数是 (O(n)) 的。

顺便如果把题目中式子的小于等于号改成小于号, 此算法也能胜任, 而且针对小于号还有一种其它的算法:
所有 ((x,y)) 都是某一个数的 ((左边第一个大于的,右边第一个大于的))

4

给定矩阵, 求 面积 最大的和 (>0) 的子矩阵。

首先枚举上下边界压缩一维(就像求最大子矩阵和一样)。

然后问题就变成了在一个数列中, 对于每个数求出最左边的小于其的数。

当然这并不是单调栈, 但是比较像单调栈。(其实是维护了前缀最小值)

总复杂度 (O(n^3log n))

原文地址:https://www.cnblogs.com/tztqwq/p/13786487.html