單調棧


單調棧,顧名思義,就是一個元素遞增(或遞減)的棧。

一個單調遞增的單調棧可以在$O(n)$的復雜度內求得序列內一個元素向左或向右第一個小於等於該元素的元素位置。

比如該序列為$1,5,2,6,4,3$

$1$進棧,棧內無元素,\(L_1=0\) \((1)\)

$5​$進棧,無出棧,\(L_2=1​\)(棧頂元素) \((1,5)​\)

$2$進棧,$5$出棧,\(R_2\)(出棧元素)\(=3\)(當前元素),\(L_3=1\) \((1,2)\)

$6$進棧,無出棧,\(L_4=3\) \((1,2,6)\)

$4​$進棧,$6​$出棧,\(R_4=5​\)\(L_5=3​\) \((1, 2, 4)​\)

$3​$進棧,$4​$出棧,\(R_5=6​\)\(L_6=5​\) \((1,2,3)​\)

棧內仍有元素,\(R_1=R_3=R_6=7\)

\[ \begin{array}{c|lcr}& \text{1} & \text{2} & \text{3}&\text{4}&\text{5}&\text{6} \\\hline L&0&1&1&3&3&5 \\R&7&3&7&5&6&7 \end{array} \]

代碼是這個樣子

int main()
{
    //讀入
    stack<int> s; int l[N], r[N];
    for (int i=1; i<=n; i++)
    {
        while ((!s.empty()) && a[s.top()]>a[i]) {r[s.top()]=i; s.pop();}
        if (!s.empty()) l[i]=s.top(); else l[i]=0;
        s.push(i);
    }
    while (!s.empty()) {r[s.top()]=n+1; s.pop();}
}

一道模板題

完整代碼:

#include<bits/stdc++.h>
using namespace std;
const int N=100005;
int a[N], l[N], r[N];
stack<int> s;

inline int read()
{
    int x=0,f=1;char ch=getchar();
    for (;ch<'0'||ch>'9';ch=getchar()) if (ch=='-') f=-1;
    for (;ch>='0'&&ch<='9';ch=getchar()) x=(x<<1)+(x<<3)+ch-'0';
    return x*f;
}

int main()
{
    int n;
    while (scanf("%d",&n)!=EOF&&n)
    {
        for (int i=1; i<=n; i++) a[i]=read();
        for (int i=1; i<=n; i++)
        {
            while ((!s.empty()) && a[s.top()]>a[i]) 
                {r[s.top()]=i; s.pop();}
            if (!s.empty()) l[i]=s.top();
                else l[i]=0;
            s.push(i);
        }
        while (!s.empty()) {r[s.top()]=n+1; s.pop();}
        long long ans=0;
        for (int i=1; i<=n; i++) ans=max(ans, 1ll*a[i]*(r[i]-l[i]-1));
        printf("%lld\n",ans);
    }
    return 0;
}

但大多數題目並不需要求出$l,r$數組,只需要用到一個棧就可以了。

另一道例題

用單調棧維護一下即可,注意一下高度相同時的細節。

#include<bits/stdc++.h>
using namespace std;
stack<pair<int, int> > s;

inline int read()
{
    int x=0,f=1;char ch=getchar();
    for (;ch<'0'||ch>'9';ch=getchar()) if (ch=='-') f=-1;
    for (;ch>='0'&&ch<='9';ch=getchar()) x=(x<<1)+(x<<3)+ch-'0';
    return x*f;
}

int main()
{
    int n=read(); long long ans=0;
    for (int i=1; i<=n; i++)
    {
        int h=read(); pair<int, int> p=make_pair(h, 1);
        for (; (!s.empty()) && s.top().first<=h; s.pop())
        {
            ans+=s.top().second;
            if (s.top().first==h) p.second+=s.top().second;
        }
        if (!s.empty()) ans++;
        s.push(p);
    }
    printf("%lld\n",ans);
    return 0;
}

單調棧還可以解決例如最大子矩陣問題,當然這個問題也有同樣復雜度的懸線法做法,在此不作討論。

然后看一道新鮮出爐的省選題

此題即求所有子矩陣的$and$和與$ or$和。

按位討論,$and$和即全部為$1$的子矩陣數量,$or$和即為子矩陣數量減去全部為$0$的子矩陣數量。

然后單調棧就可以$O(n^2)​$解決了,細節需要想想。

#include<cstdio>
#define rep(i, a, b) for (register int i=(a); i<=(b); ++i)
#define per(i, a, b) for (register int i=(a); i>=(b); --i)
using namespace std;
const int N=1005, P=1000000007;
inline int max(int a, int b){return a>b?a:b;}
int a[N][N], h[N], s[N], cnt[N], n, top, And, Or;

inline int read()
{
    int x=0,f=1;char ch=getchar();
    for (;ch<'0'||ch>'9';ch=getchar()) if (ch=='-') f=-1;
    for (;ch>='0'&&ch<='9';ch=getchar()) x=(x<<1)+(x<<3)+ch-'0';
    return x*f;
}

int calc(int n){return 1ll*n*(n+1)>>1;}

int solve(int x)
{
    int res=0;
    rep(i, 1, n) h[i]=0;
    rep(i, 1, n) 
    {
        rep(j, 1, n)
        {
            if ((a[i][j]&1)==x) h[j]++; else h[j]=0;
            if (h[j]>s[top]) s[++top]=h[j], cnt[top]=1;
            else
            {
                int tmp=0;
                while (s[top]>h[j]) 
                {
                    tmp+=cnt[top];
                    res=(res+1ll*(s[top]-max(h[j], s[top-1]))*calc(tmp)%P)%P;
                    --top;
                }
                s[++top]=h[j]; cnt[top]=tmp+1;
            }
        }
        int tmp=0;
        while (top)
        {
            tmp+=cnt[top];
            res=(res+1ll*calc(tmp)*(s[top]-s[top-1])%P)%P;
            --top;
        }
    }
    return res;
}

int main()
{
    n=read();
    rep(i, 1, n) rep(j, 1, n) a[i][j]=read();
    rep(i, 0, 31) 
    {
        And=(And+1ll*solve(1)*(1<<i)%P)%P;
        Or=(Or+1ll*(1ll*calc(n)*calc(n)%P-solve(0))*(1<<i))%P;
        rep(j, 1, n) rep(k, 1, n) a[j][k]>>=1;
    }
    printf("%d %d\n", And, (Or+P)%P);
    return 0;
}

單調棧的思想在各種各樣的題目中都有體現,比如某些題推出了結論然后可以用單調棧來維護,比如牛客的這題這題的$50$分

牛客的這題結論就是就是用一段一段的$A$和一段一段的$B$來接成$C$,使得每一段的平均值嚴格遞增。證明不會,貌似可以感性認知一下不能通過交換位置構造出更優解~~(反正這又不是本文重點)~~。

另一題也是分段使每一段平均值遞增~~(好像還看過類似的套路,但題目不記得了~~

貼一個牛客那題的代碼

#include<bits/stdc++.h>
#define int long long
#define rep(i, a, b) for (register int i=(a); i<=(b); ++i)
#define per(i, a, b) for (register int i=(a); i>=(b); --i)
using namespace std;
const int N=100005;
struct node{int sum, cnt;}s1[N], s2[N];
bool operator < (node a, node b){return a.sum*b.cnt<b.sum*a.cnt;}
node operator + (node a, node b){return (node){a.sum+b.sum, a.cnt+b.cnt};}
int a[N], b[N], c[N<<1], cnt1, cnt2, tot, ans;
 
inline int read()
{
    int x=0,f=1;char ch=getchar();
    for (;ch<'0'||ch>'9';ch=getchar()) if (ch=='-') f=-1;
    for (;ch>='0'&&ch<='9';ch=getchar()) x=(x<<1)+(x<<3)+ch-'0';
    return x*f;
}
 
signed main()
{
    int n=read(), m=read();
    rep(i, 1, n) a[i]=read(); rep(i, 1, m) b[i]=read();
    rep(i, 1, n)
    {
        s1[++cnt1]=(node){a[i], 1};
        while (cnt1 && s1[cnt1-1]<s1[cnt1])
            s1[cnt1-1]=s1[cnt1-1]+s1[cnt1], cnt1--;
    }
    rep(i, 1, m)
    {
        s2[++cnt2]=(node){b[i], 1};
        while (cnt2 && s2[cnt2-1]<s2[cnt2])
            s2[cnt2-1]=s2[cnt2-1]+s2[cnt2], cnt2--;
    }
    int l1=1, l2=1, L1=1, L2=1;
    s1[cnt1+1].sum=s2[cnt2+1].sum=-1;
    s1[cnt1+1].cnt=s2[cnt2+1].cnt=1;
    while (l1<=cnt1 || l2<=cnt2)
        if (s2[l2]<s1[l1])
        {
            rep(i, L1, L1+s1[l1].cnt-1) c[++tot]=a[i];
            L1+=s1[l1++].cnt;
        }
        else
        {
            rep(i, L2, L2+s2[l2].cnt-1) c[++tot]=b[i];
            L2+=s2[l2++].cnt;
        }
    rep(i, 1, n+m) ans+=i*c[i]; printf("%lld\n", ans);
    return 0;
}

請忽略#define int long long

單調棧貌似還能跟數據結構一起出現?反正我不費啦。


免責聲明!

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



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