單調棧
定義:內部元素滿足單調性的棧。
用途:線性時間內處理出數組中每一個 \(i\) 左邊/右邊 第一個 大於/小於 \(a_i\) 的位置。
模板題:P5788 【模板】單調棧
題意:令 \(f(i)\) 為 \(i\) 右邊第一個大於 \(a_i\) 的位置。輸出 \(f(i)\) , \(i = 1...n\) 。
解法:"右邊"說明單調棧處理需要倒序,第一個大於說明單調棧需要維護由棧頂到棧底遞增。所以我們用一個 pair 類型的 stack 來完成即可,first 用來存 \(a_i\) , second 用來存 \(i\) 。
時間復雜度:
很顯然是 \(O(n)\) 。
經典例題:
\(No.1\)
對於 \(h_i\) 找到其左邊第一個小於其高度的樓棟記為 \(l_i\) ,右邊第一個小於其高度的樓棟記為 \(r_i\) ,那么答案就為 \(\max\) { \(a_i \cdot (r_i - l_i - 1)\) } 。
\(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\) 同理,不再贅述。
\(No.3\)
其實就是將上面一題從一維抽象成二維。
先預處理一個 \(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\) ,然后將該列的該點以上的值全部從棧中退出即可。
說起來確實有點抽象,可以結合代碼理解。
