1. 程式人生 > >[模板]單調佇列與單調棧

[模板]單調佇列與單調棧

目錄

單調佇列:滑動視窗(算是重要的板題了)

單調棧:最大矩形面積

總結


首先給一道板題:滑動視窗(十分重要,基本後面的複雜題由此題思路進行優化)

題目描述

給你一個長度為N的陣列,一個長為K的滑動的窗體從最左移至最右端,你只能見到視窗的K個數,每次窗體向右移動一位,如下圖:

 你的任務是找出窗體在各位置時的最大值和最小值。

輸入

第1行:2個整數N,K(K<=N<=1000000) 第2行:N個整數,表示陣列的N個元素(<=2*10^9)

輸出

第1行:滑動視窗從左向右移動每個位置的最小值,每個數之間用一個空格分開 第2行:滑動視窗從左向右移動每個位置的最大值,每個數之間用一個空格分開

樣例輸入

8 3
1 3 -1 -3 5 3 6 7

樣例輸出

-1 -3 -3 -3 3 3
3 3 5 5 6 7

解題思路

既然都是板題了,那麼便考慮一下怎麼用單調佇列來解決吧。

首先說明一下單調佇列:

單調佇列顧名思義就是佇列中的元素是單調的(即單調上升單調下降。具體維護哪種因題而異)。佇列需雙邊進出,我們通常用陣列進行模擬,想來也要好操作一些。這道題想了一下如果用STL的模板好像不能夠進行操作。

這道題我們是要兩個單調佇列,一個維護上升,一個維護下降。先說一下維護下降的做法吧(相信讀者知道下降就知道怎麼弄上升了)

其實方法很簡單,一個元素需要放進佇列的時候我們就需要將它與佇列中的元素進行比較。如果a[i]>此時佇列的元素,那麼這一個元素便出隊。反之,將a[i]放進佇列。對頂元素就是其最大值,但由於這道題還有一個區間限制,我們還需while判斷一下對頂元素是否在這一個區間內,如果不在。對頂便要出去。所以我們這道題陣列模擬的佇列要用結構體來進行儲存。可以在輸入時嘗

試構造這一個單調佇列,不斷維護,每一次維護後,每一個區間的答案便出來了。

#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<vector>
#include<map>
#include<iostream>
#define M 1000005
using namespace std;
struct node {
    int x, id ;
    node (){};
    node (int X,int ID){
        x = X;
        id = ID;
    }
};
node mx[M] , mi[M];
int k , n , a[M] , head1 = 1 , tail1 , head2 = 1 , tail2 , ans1[M] , ans2[M];
int main(){
    scanf ("%d%d",&n , &k);
    for (int i = 1;i <= n; i ++ ){
        scanf ("%d",&a[i]);
        while (tail1 >= head1){//對要新裝進佇列的元素進行處理(維護遞減)
            if (a[i] > mx[tail1].x){//不滿足單調性,彈出佇列中這一個元素
                tail1 -- ;
            }
            else{
                mx[++ tail1] = node (a[i],i);//找到了合適位置,放入佇列
                break;
            }
        }
        if (tail1 < head1)//新要加進的元素彈出來所有點,我們要特殊處理
            mx[++ tail1 ] = node (a[i],i);
        while  (mx[head1].id <= i - k){//判斷隊頂元素是否在這一個區間
            head1 ++ ;
        }
        while (tail2 >= head2 ){//對要新裝進佇列的元素進行處理(維護遞增)
            if (a[i] <= mi[tail2].x){
                tail2 -- ;
            }
            else{
                mi[++ tail2] = node (a[i],i);
                break;
            }
        }
        if (tail2 < head2)//新要加進的元素彈出來所有點,我們要特殊處理
            mi[++ tail2] = node (a[i],i);
        while (mi[head2].id <= i - k)//判斷隊頂元素是否在這一個區間
            head2 ++ ;
        ans1 [i] = mx[head1].x;//儲存最大值答案
        ans2 [i] = mi[head2].x;//儲存最小值答案
    }
    for (int i = k;i < n;i ++ ){
        printf("%d ",ans2[i]);
    }
    printf("%d\n",ans2[n]);
    for (int i = k;i < n;i ++ ){
        printf("%d ",ans1[i]);
    }
    printf("%d\n",ans1[n]);
}

 

再來一道有關單調棧的題吧。

說實話單調棧沒啥用,單調佇列依舊能夠做到。

最大矩形面積

不想複製了,截圖吧

解題思路

用單調棧,單調棧跟單調佇列其實是一個意思,只不過只有一邊進出。

我們思考一下如果i為子矩陣中高度最小的矩陣,那麼i所能構造的最大子矩陣也就是i左邊第一個比它矮到i右邊第一個比它矮的距離再乘上i的高度,那麼原題便轉換成了max(f[i] * h[i])(f[i]表示i左邊第一個比它矮到i右邊第一個比它矮的距離,h[i]表示i點的高度。)那麼最關鍵的就是求i的l與r了,這時候單調棧(佇列)就派上用場了。

首先確定思路,我們維護棧內元素呈遞增,那麼裝進h[i]時,便需要在棧裡面彈東西。最後找到適合的放置位置便可以裝入棧退出了。於是在這樣一種操作中就可以求出f[i]了,模擬一下可以知道,當i進棧時,便找到了l,當i出棧時,便找到了r。

於是這道題就可以解決了。

#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<vector>
#include<queue>
#include<map>
#include<stack>
#define MAXN 100005
using namespace std;
struct node {
    int x,id;
    node (){};
    node(int X,int ID){
        x = X;
        id = ID;
    }
};
stack<node>Q;
int n , sum[MAXN] , ans , l[MAXN] , r[MAXN];
inline void read(int &x){
    int f = 1;
    x = 0;
    char s = getchar();
    while (s < '0'|| s > '9'){
        if (s == '-')
            f = -1;
        s = getchar ();
    }
    while (s >= '0' && s <= '9'){
        x = x * 10 + s - '0';
        s = getchar();
    }
    x *= f ;
}
int main(){
    scanf ("%d",&n);
    for (int i =  1;i <= n; i ++ ){
        scanf ("%d",&sum[i]);
    }
    for (int i = 1;i <= n + 1; i ++ ){
        while (!Q.empty()){
            node X = Q.top();
            if (X.x < sum[i]){
                Q.push(node(sum[i],i));
                l[i] = X.id + 1;
                break;
            }
            else{
                    Q.pop();
                    r[X.id] = i - 1;
                    ans = max (ans,(r[X.id] - l[X.id] + 1) * sum [X.id]);
            }
        }
        if (Q.empty() && i != n + 1){
            Q.push(node(sum[i],i));
            l[i] = 1;
        }
    }
    printf ("%d",ans);
}

 

總結

總體來說單調佇列還是用的比較多,我們用陣列模擬便可以很輕鬆地實現兩種。單調佇列其實是有關DP的優化的。因此除了模板,單調佇列一般要與DP所結合。如何要能夠想到用單調佇列的思想來做呢。就需要找DP的玄機了,DP是由上一狀態轉到現在這個狀態,我們有時可以進行拆分,發現一些定值,另一個變值肯定是最後要求個最大值(最小值)。一般來說這都是二維,第一維估計是定值,第二維一般都是變值,我們就可以通過在第一維這一個線性中,運用單調佇列來進行優化了。這只是一般情況(畢竟我遇到的題尚少)。難點的就有一道NOI的瑰麗華爾茲了。