基本算法
簡介
單調棧是一種算法, 它可以用一次掃描 \(O(n)\) 時間求出序列中每個數向左和向右的第一個大於它的數, 也可以用一次掃描 \(O(n)\) 時間求出序列中每個數向左和向右的第一個小於它的數。
以大於為例:
用單調棧求左邊第一個大於的數:
有數列\(\big<a\big>\)。
對於一個數 \(a_i\) 前面的兩個數 \(a_p,a_q, \quad p<q<i\), 若 \(a_p<a_q\), 則對於所有 \(j\ge i\), \(a_p\) 都不可能成為 \(a_j\) 的答案。
單調棧就是對於每個數維護可能成為其答案的下標集合, 然后從里面快速選出答案。
具體地, 算法流程是:
一、 從左到右掃描數列, 開一個空棧 \(stk\), 棧頂為 \(stk[tp]\)
二、對於每個數 \(a_i\),
-
在棧不為空且 \(stk[tp] \le a_i\) 的時候使棧不斷 \(pop\)
-
停止 \(pop\) 后, 若棧不為空, \(stk[tp]\) 就是 \(a_i\) 左邊第一個大於其的數, 否則 \(a_i\) 左邊沒有大於其的數。
-
將 \(a_i\) 加入到棧頂
從以上算法可以看出, 每個數 \(a_i\) 都是會被加入棧里的, 那么在以后的掃描過程中, 讓 \(a_i\) 從棧中彈出的那個數一定就是 \(a_i\) 右邊第一個大於其的數, 若自始至終 \(a_i\) 沒有被彈出, 則 \(a_i\) 右邊沒有大於其的數。
至此, 可以用單調棧一次掃描求出每個數左邊和右邊第一個大於其的數, 對於小於, 只需簡單地魔改一下算法就可以了, 用到的理論也是高度類似的
對於基本算法的正確性的證明
需要證明的僅僅是求左邊第一個大於的數的正確性, 懂的都懂。
那么對於每個數 \(a_i\), 在 算法流程 掃描到 \(a_i\), 執行到 二-2 的時候, 為什么能直接確定答案?
首先可以確定棧中沒有不可能成為 當前數的答案 的數 (當然也沒有不可能成為以后數的答案的數), 此時選棧頂, 即最靠近當前數的數, 就是答案。
為什么可以確定這個事實? 可以用歸納法證明, 在這里就不證了, 僅給出參考思路:
假設掃描到 \(a_i\),
- 用歸納法證明到步驟 二-1 之前,棧中沒有不可能成為 \(a_i\) 答案的數, 所有可能成為 \(a_i\) 答案的數都在棧中。
- 用歸納法證明到步驟 二-1 之前,從棧底到棧頂是一個單調遞減的數列, 這就可以證明執行完步驟 二-1 之后棧頂就是答案
更深一點的應用
可以用歸納法證明, 對於數列 \(\big<a\big>\), 在使用基本算法求每個數的左邊的第一個大於其的數時, 對於當前掃描的數 \(a_i\), 單調棧實際上是維護的數列 \(\big<a\big>_{1\cdots i}\) 的后綴最大值(從棧頂到棧底, 就是 \(max(a[i\cdots i])、max(a[i-1\cdots i])、\cdots、max(a[1\cdots i])\) 去重后的結果)。
於是在單調棧的過程中, 可以見到整個數列所有子串的最大值, 至於有多少應用嘛……不知道。
應用
1
求 \(\sum_{l=1}^n\sum_{r=l}^n max(a_{l\cdots r})\)。
根據上面的 更深一點的應用 單調棧 \(O(n)\) 搞, 很不錯。
還可以枚舉所有 \(a_i\), 算出 max 是 \(a_i\) 的區間有多少個, 這個也可以用單調棧求出來。
1的升級版
\(f(X,Y) = \sum_{l=X}^Y\sum_{r=l}^Y max(a_{l\cdots r})\)
\(Q\) 次詢問不同的 \(f(X,Y)\)
要求 \(O((n+Q)\log n)\)。
似乎不太可做, 待補。
2
給定數列 \(a\) , 求 \(max\{a[L\cdots R]\}*(a_R-a_L)\) 的最大值。
枚舉 \(max\{a[L\cdots R]\}\), 再討論一下, 就做完了。(用 st表 可以做到 \(O(n\log n)\))
如果把 \(a_R-a_L\) 換成 \(R-L+1\), 可以做到 \(O(n)\)。
3
給定數列 \(a\), 求有幾對 \((x,y)\) 滿足:
\(max(a[x+1\cdots y-1]) \le min(a[x],a[y])\)。
順便可以求出具體方案。
單調棧求左邊第一個大於的數的過程中, 被一個數彈掉的所有數以及此數的答案, 就是所有可以使這個數作為 \(y\) 的 \(x\)。
這樣的對數是 \(O(n)\) 的。
順便如果把題目中式子的小於等於號改成小於號, 此算法也能勝任, 而且針對小於號還有一種其它的算法:
所有 \((x,y)\) 都是某一個數的 \((左邊第一個大於的,右邊第一個大於的)\)。
4
給定矩陣, 求 面積 最大的和 \(>0\) 的子矩陣。
首先枚舉上下邊界壓縮一維(就像求最大子矩陣和一樣)。
然后問題就變成了在一個數列中, 對於每個數求出最左邊的小於其的數。
當然這並不是單調棧, 但是比較像單調棧。(其實是維護了前綴最小值)
總復雜度 \(O(n^3\log n)\)