簡單的單調棧算法



基本算法

簡介

單調棧是一種算法, 它可以用一次掃描 \(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\)

  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 之后棧頂就是答案

更深一點的應用

可以用歸納法證明, 對於數列 \(\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)\)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM