單調棧詳解


單調棧

定義:內部元素滿足單調性的棧。

用途:線性時間內處理出數組中每一個 \(i\) 左邊/右邊 第一個 大於/小於 \(a_i\) 的位置。

模板題:P5788 【模板】單調棧

題意:令 \(f(i)\)\(i\) 右邊第一個大於 \(a_i\) 的位置。輸出 \(f(i)\) , \(i = 1...n\)

解法:"右邊"說明單調棧處理需要倒序,第一個大於說明單調棧需要維護由棧頂到棧底遞增。所以我們用一個 pair 類型的 stack 來完成即可,first 用來存 \(a_i\) , second 用來存 \(i\)

時間復雜度:

很顯然是 \(O(n)\)

Code

經典例題:

\(No.1\)

AcWing131. 直方圖中最大的矩形

對於 \(h_i\) 找到其左邊第一個小於其高度的樓棟記為 \(l_i\) ,右邊第一個小於其高度的樓棟記為 \(r_i\) ,那么答案就為 \(\max\) { \(a_i \cdot (r_i - l_i - 1)\) } 。

Code

\(No.2\)

P6503 [COCI2010-2011#3] DIFERENCIJA

考慮動態規划,\(fmax_i\) 表示以 \(i\) 為結尾的區間的 \(\max\) 之和, \(fmin_i\) 表示以 \(i\) 為結尾的區間的 \(\min\) 之和。

對於維護 \(fmax\) ,我們可以知道 \(a_i\) 最多影響到其前一個比它大的數位置 $ + 1$ 的位置。所以我們用單調棧維護每一個 \(a_i\) 前一個比它大的數的位置,記這個位置為 \(nxt_i\) ,那么轉移即為即為 \(fmax_i = fmax_{nxt_i} + a_i \cdot (i - nxt_i)\)

對於 \(fmin_i\) 同理,不再贅述。

Code

\(No.3\)

P1950 長方形

其實就是將上面一題從一維抽象成二維。

先預處理一個 \(f_{i,j}\) 表示第 \(i\) 行以 \((i,j)\) 為結尾的連續 "." 的最前面一個 "." 的位置:

for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++)
        f[i][j] = j;
for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++)
        if(ch[i][j - 1] == '.')f[i][j] = f[i][j - 1];

然后用 \(ans_{i,j}\) 表示以 \((i,j)\) 為左下角能構成的矩形數。

我們該怎么推狀態轉移方程呢。

舉兩個例子吧:(原題的 \(.\) 用 # 代替)

* * #
* # #
# # #
# # #

這種情況 \(ans_{4 , 3} = ans_{2 , 3} + (4 - 2) \times (3 - f_{4,2} + 1)\)

# # #
# # #
* # #
* * #

這種情況

\(ans_{3 , 3} = 2 \times 3 = 6\)

\(ans_{4 , 3} = 1 \times 4 = 4\)

仔細思考,對於某一 # 點 \((i, j)\) ,考慮找到在它之上離它最近的 \(f\) 值大於它的點,記這個點為 \((x ,j)\)

\(ans_{i,j} = ans_{x,j} + (i - x) \times (j - f_{i,j} + 1)\)

這個式子建議配合以上的例子仔細思考,我認為還是有一定思維難度的。

而對於一個 \(*\) 點,可以把它視為一堵牆,或者理解成邊界。遇到它我們只需要把 \(ans_{i,j}\) 賦為 \(0\) ,然后將該列的該點以上的值全部從棧中退出即可。

說起來確實有點抽象,可以結合代碼理解。

Code


免責聲明!

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



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