单调栈

单调栈,顾名思义,就是一个元素递增(或递减)的栈。

一个单调递增的单调栈可以在\(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

单调栈貌似还能跟数据结构一起出现?反正我不费啦。

posted @ 2019-04-19 20:47  OIerC  阅读(873)  评论(0编辑  收藏  举报