單調(diào)棧和單調(diào)隊(duì)列

單調(diào)棧:進(jìn)棧元素單調(diào)遞增(減)的棧漾月,如果碰到比棧頂元素大的元素就進(jìn)棧病梢,否則不斷把棧頂元素彈出直到棧頂元素小于等于要進(jìn)棧的元素或者棧為空。
應(yīng)用:
已知一個(gè)數(shù)列梁肿,求每一個(gè)元素后面第一個(gè)比它大的元素到它的距離蜓陌,如果沒(méi)有,輸出0吩蔑。此類問(wèn)題僅和元素下標(biāo)差以及元素的值有關(guān)钮热,而且問(wèn)題具有對(duì)下標(biāo)和對(duì)值的單調(diào)性,可以用這種方法把O(N^2)的算法變成O(N)的算法烛芬。

#include<iostream>
#include<stack>
using namespace std;
int n;
int a[100010];
int res[100010];
typedef struct {
    int t;
    int pos;
}T;
stack<T> s;
T now, temp;
int main() {
    while (cin >> a[n++]);
    n--;
    for (int i = 0; i < n; i++) {
        now.pos = i;
        now.t = a[i];
        if (s.empty() || s.top().t > a[i]) {
            s.push(now);
        }
        else if (s.top().t == a[i]) continue;
        else {
            while (!s.empty() && s.top().t <= a[i]) {
                temp = s.top();
                s.pop();
                res[temp.pos] = i - temp.pos;
            }
            s.push(now);
        }
    }
    while (!s.empty()) {
        res[s.top().pos] = 0;
        s.pop();
    }
    for (int i = 0; i < n; i++) cout << res[i] << ' ';
    return 0;
}

例題:
最大長(zhǎng)方形:http://poj.org/problem?id=2559(原題的網(wǎng)站上不去)
先看最大正方形:https://www.luogu.org/problem/P1387
已知一個(gè)0-1矩陣a[M][N]隧期,求只包含1的最大正方形的面積。
設(shè)dp[i][j]為以(i,j)為右下角的最大正方形邊長(zhǎng)赘娄,易知第一行和第一列的0方格的dp值為0仆潮,1方格的dp值為1。
對(duì)于其他的方格遣臼,如果它是0方格性置,dp值為0;如果它是1方格揍堰,則考慮它的左邊鹏浅,上邊和左上邊,如果有任意一個(gè)方格為0屏歹,則它的dp值為1(只有它自己)隐砸,否則它的dp值為相鄰三個(gè)方格dp值的最小值再加1,因?yàn)樽鳛樽钚≈祄in西采,說(shuō)明它左上方的方格的左上方至少有一個(gè)min*min的正方形凰萨,它上方和左方的方格一定能向上和向左擴(kuò)展出min,再加上它自己,邊長(zhǎng)為min+1胖眷,即:
dp[i][j]=min(dp[i-1][j],dp[i][j-1],dp[i-1][j-1])+1
動(dòng)態(tài)規(guī)劃即可武通。

#include<iostream>
#include<algorithm>
using namespace std;
int h, w;
bool a[1405][1405];
int dp[1405][1405];
int res;
int main() {
    cin >> h >> w;
    for (int i = 0; i < h; i++) {
        for (int j = 0; j < w; j++) {
            cin >> a[i][j];
            a[i][j] = !a[i][j];
        }
    }
    for (int i = 0; i < h; i++) {
        if (!a[i][0]) dp[i][0] = 1;
    }
    for (int j = 0; j < w; j++) {
        if (!a[0][j]) dp[0][j] = 1;
    }
    for (int i = 1; i < h; i++) {
        for (int j = 1; j < w; j++) {
            if (a[i][j]) dp[i][j] = 0;
            else {
                dp[i][j] = min(dp[i - 1][j], min(dp[i][j - 1], dp[i - 1][j - 1])) + 1;
                res = max(res, dp[i][j]);
            }
        }
    }
    cout << res;
    return 0;
}

時(shí)間復(fù)雜度:O(MN)
最大長(zhǎng)方形:把上題的正方形改成長(zhǎng)方形珊搀。
動(dòng)態(tài)規(guī)劃不對(duì)了冶忱,因?yàn)殚L(zhǎng)方形長(zhǎng)寬不一樣囚枪。
最樸素的想法:枚舉所有點(diǎn)劳淆,找它們能構(gòu)成的最大長(zhǎng)方形面積的最大值。聯(lián)想關(guān)燈問(wèn)題括勺,可以按順序做曲掰,枚舉每一行以[i,j]為底的高最短的長(zhǎng)方形面積,對(duì)于高可以用預(yù)處理算出來(lái)乱豆,即從上往下算最多能連續(xù)多少個(gè)1宛裕,顯然有dp[i]=dp[i-1]+1趾徽,用動(dòng)態(tài)規(guī)劃容易算得,問(wèn)題轉(zhuǎn)化為求直方圖上的最大長(zhǎng)方形面積疲酌,預(yù)處理的時(shí)間復(fù)雜度為O(MN)朗恳。
然后遍歷每一行载绿,每一行再遍歷每一組[i,j],去最小高乘寬度再取最大值怀浆,時(shí)間復(fù)雜度為O(MN^2)
下面用單調(diào)棧把復(fù)雜度降到O(MN)
棧儲(chǔ)存直方圖上長(zhǎng)方形的高和左端的位置pos镰踏。
對(duì)每一行沙合,從左往右枚舉元素:
若棧為空或者棧頂元素的高小于要入棧元素的高,就入棧绊率。
否則出棧到棧頂元素的高小于要入棧元素的高為止究履,設(shè)置pos為最后一個(gè)出棧元素的pos最仑,入棧,在這個(gè)過(guò)程中,對(duì)于每一個(gè)出棧的元素蜜葱,都計(jì)算它和已出棧元素所能形成的最大長(zhǎng)方形的面積:(i-pos)*h,直到所有元素都入過(guò)棧爸黄,把棧內(nèi)的元素清空揭鳞。
這樣實(shí)際上每一行只遍歷了一遍,時(shí)間復(fù)雜度為O(MN)称开。
部分代碼(求柱狀圖的最大矩形鳖轰,能過(guò)poj2559):

#pragma warning(disable:4996)
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<stack>
using namespace std;
typedef long long ll;
const int maxn = 100010;
int n;
ll a[maxn];
ll res;
typedef struct {
    ll pos;
    ll h;
}rect;
stack<rect> s;
int main() {
    rect now, temp;
    while (1) {
        scanf("%d", &n);
        if (n == 0) break;
        for (int i = 0; i < n; i++) scanf("%I64d", &a[i]);
        res = 0;
        a[n] = -1;
        for (int i = 0; i <= n; i++) {
            now.pos = i;
            now.h = a[i];
            if (s.empty() || a[i] > s.top().h) {
                s.push(now);
            }
            else if (a[i] == s.top().h) continue;
            else {
                temp.pos = i;
                while (!s.empty() && a[i] <= s.top().h) {
                    temp = s.top();
                    s.pop();
                    res = max(res,ll((i - temp.pos)*temp.h));
                }
                now.pos = temp.pos;
                s.push(now);
            }
        }
        printf("%I64d\n", res);
    }

    return 0;
}

單調(diào)隊(duì)列:https://www.luogu.org/problem/P1886
連續(xù)移動(dòng)區(qū)間的最值問(wèn)題蕴侣。
首先想到可以用樹狀數(shù)組臭觉,時(shí)間復(fù)雜度為O(NlogN)辱志,似乎很好揩懒,但是此題N=10^6客冈,NlogN=2*10^7场仲,這樣還是會(huì)超時(shí)。
1s的時(shí)間限制鸽素,O(N)對(duì)應(yīng)10^6及以上亦鳞,O(NlogN)對(duì)應(yīng)10^5O(N^2)對(duì)應(yīng)10^3遭笋。
用單調(diào)隊(duì)列可以把時(shí)間復(fù)雜度降到O(N)瓦呼。
如果隊(duì)列為空测暗,從隊(duì)尾進(jìn)隊(duì);否則比較要進(jìn)隊(duì)的元素和隊(duì)尾元素质和,如果它比隊(duì)尾元素大稚字,就進(jìn)隊(duì),否則出隊(duì)直到隊(duì)尾元素比它小褒傅,進(jìn)隊(duì)袄友。如果要進(jìn)隊(duì)的元素和隊(duì)首元素下標(biāo)差大于等于k,隊(duì)首元素從隊(duì)首出隊(duì)(相當(dāng)于滑動(dòng))支竹。
STL實(shí)現(xiàn):

#include<iostream>
#include<deque>
using namespace std;
int n, k;
const int maxn = 1000010;
struct S {
    int pos;
    int val;
}a[maxn];
deque<S> q;
int main() {
    cin >> n >> k;
    for (int i = 1; i <= n; i++) {
        cin >> a[i].val;
        a[i].pos = i;
    }
    for (int i = 1; i <= n; i++) {
        while (!q.empty() && q.back().val >= a[i].val) q.pop_back();
        q.push_back(a[i]);
        if (a[i].pos - q.front().pos >= k) q.pop_front();
        if (i >= k) cout << q.front().val << ' ';
    }
    cout << endl;
    q.clear();
    for (int i = 1; i <= n; i++) {
        while (!q.empty() && q.back().val <= a[i].val) q.pop_back();
        q.push_back(a[i]);
        if (a[i].pos - q.front().pos >= k) q.pop_front();
        if (i >= k) cout << q.front().val << ' ';
    }
    return 0;
}

數(shù)組實(shí)現(xiàn):一定要記住設(shè)tail=0,head=1,判斷非空的條件是head<=tail礼搁,不這樣的話會(huì)出現(xiàn)++tail首元素沒(méi)讀,tail++從隊(duì)首出隊(duì)錯(cuò)誤扎运。

#include<iostream>
using namespace std;
const int maxn = 1000010;
int n, k;
struct S {
    int pos;
    int val;
}a[maxn], q[maxn];
int head, tail;
int main() {
    cin >> n >> k;
    for (int i = 1; i <= n; i++) {
        cin >> a[i].val;
        a[i].pos = i;
    }
    head = 1;
    for (int i = 1; i <= n; i++) {
        while (head <= tail && q[tail].val >= a[i].val) tail--;
        q[++tail] = a[i];//注意是++tail饮戳,不是tail++
        if (a[i].pos - q[head].pos >= k) head++;
        if (i >= k) cout << q[head].val << ' ';
    }
    cout << endl;
    head = 1;
    tail = 0;
    for (int i = 1; i <= n; i++) {
        while (head <= tail && q[tail].val <= a[i].val) tail--;
        q[++tail] = a[i];
        if (a[i].pos - q[head].pos >= k) head++;
        if (i >= k) cout << q[head].val << ' ';
    }
    return 0;
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末扯罐,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子掩浙,更是在濱河造成了極大的恐慌秸歧,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,039評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件遣蚀,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡险耀,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門蘑志,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)急但,“玉大人搞乏,你說(shuō)我怎么就攤上這事∏攵兀” “怎么了储玫?”我有些...
    開封第一講書人閱讀 165,417評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵撒穷,是天一觀的道長(zhǎng)端礼。 經(jīng)常有香客問(wèn)我入录,道長(zhǎng),這世上最難降的妖魔是什么喻括? 我笑而不...
    開封第一講書人閱讀 58,868評(píng)論 1 295
  • 正文 為了忘掉前任贫奠,我火速辦了婚禮,結(jié)果婚禮上拷恨,老公的妹妹穿的比我還像新娘谢肾。我一直安慰自己,他們只是感情好冕杠,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,892評(píng)論 6 392
  • 文/花漫 我一把揭開白布分预。 她就那樣靜靜地躺著,像睡著了一般笼痹。 火紅的嫁衣襯著肌膚如雪酪穿。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,692評(píng)論 1 305
  • 那天救赐,我揣著相機(jī)與錄音净响,去河邊找鬼。 笑死馋贤,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的配乓。 我是一名探鬼主播,決...
    沈念sama閱讀 40,416評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼崎页,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼飒焦!你這毒婦竟也來(lái)了屿笼?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,326評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤休雌,失蹤者是張志新(化名)和其女友劉穎肝断,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體担扑,經(jīng)...
    沈念sama閱讀 45,782評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡涌献,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,957評(píng)論 3 337
  • 正文 我和宋清朗相戀三年羔挡,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了绞灼。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片呈野。...
    茶點(diǎn)故事閱讀 40,102評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖军掂,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情蝗锥,我是刑警寧澤,帶...
    沈念sama閱讀 35,790評(píng)論 5 346
  • 正文 年R本政府宣布汇竭,位于F島的核電站穴张,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏玻驻。R本人自食惡果不足惜偿枕,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,442評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望彪蓬。 院中可真熱鬧捺萌,春花似錦、人聲如沸桃纯。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至玫氢,卻和暖如春谜诫,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背喻旷。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留烙无,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,332評(píng)論 3 373
  • 正文 我出身青樓截酷,卻偏偏與公主長(zhǎng)得像合搅,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子灾部,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,044評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容