单调栈的应用

单调栈,顾名思义,就是存放的元素是单调的栈。

单调栈主要是用来计算某个数左边/右边第一个比它小/大的数,基本上所有单调栈的题都是用到单调栈的这个功能

下面以计算某个数左边第一个比它小的数为例,如果是暴力,对于一个数而言,复杂度是可以接受的,但是倘若要对一个数组所有的数字都计算左边第一个比他小的数呢?

于是要利用单调栈。观察一串序列 3 9 7 4 5 我们要计算每个数左边第一个比他小的数(没有则输出-1) ,显然,第一个数的答案是-1,将3压入单调栈。

计算数字9 时,我们发现单调栈的栈顶元素比9小,于是9的答案是3 ,后将9压入栈顶。

计算数字7时,我们发现单调栈栈顶的元素是9,大于7,不是我们需要的数。这个时候,我们可以直接将9弹出栈,因为9不是我们当前的答案,且数字9不会成为之后数字的答案(因为7比9小,且在9的右边),于是,我们可以删去很多数字。将9弹出后,栈顶元素3符合要求,于是7的答案是3。 随后将7压入单调栈。

同理,当遇见数字4时,栈顶元素7比4大,因此可以直接弹出7,然后栈顶元素3小于4,为答案,最后将4弹入栈。

计算数字5时,栈顶元素4符合要求,因此答案为4,随后将5 弹入栈。

因此,在维护单调栈时,我们只要把栈中比当前大的元素删除,最后的栈顶即为答案。
acwing例题1:单调栈

#include <iostream>
using namespace std;
const int N = 100010;
int a[N];
int s[N],tt=0;
int main()
{
    int n;cin>>n;
    for(int i=1;i<=n;i++)scanf("%d",&a[i]);
    for(int i=1;i<=n;i++)
    {
        while(a[i]<=s[tt]&&tt>0)tt--;
        if(tt==0)cout<<"-1 ";
        else cout<<s[tt]<<" ";
        s[++tt] = a[i];
    }
    return 0;
}

acwing例题2:城市游戏
思路:我们对每一行进行枚举,在每一行中,我们记录l[i] 以当前行为底部,第i列开始往左边所能形成的最大面积,r[i] 为以当前行为底部,第i列往右边所能形成的最大面积。
以l[i] 为例,i列 的F高度为b[i],即寻找当前行左边第一个小于b[i] 的值。
r[i]同理

#include <bits/stdc++.h>

#define pb push_back
#define debug(x) cerr<<#x<<'='<<x<<'\n'
#define debugg(x,y) cerr<<#x<<'='<<x<<','<<#y<<'='<<y<<'\n' 
#define inf 0x3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int N = 1010 ,M = 1000010,mod = 998244353;
ll qmi(ll,ll);ll read();ll gcd(ll,ll);
int n,m;
char s[N][N];
int f[N][N];
int b[N];
int stk[N],tt,l[N],r[N];
signed main()
{
	int n,m;cin>>n>>m;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            scanf(" %c",&s[i][j]);
    int res = 0;
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
            if(s[i][j]=='F')
                b[j]++;
            else b[j] = 0;
        tt = 0;
        stk[0] = 0;
        for(int j=1;j<=m;j++)
        {
            while(tt&&b[stk[tt]]>=b[j])
                --tt;
            l[j] = (j-stk[tt])*b[j];
            stk[++tt] = j;
        }
        tt = 0;
        stk[0] = m+1;
        for(int j = m;j>=1;j--)
        {
            while(tt&&b[stk[tt]]>=b[j])
                --tt;
            r[j] = (stk[tt]-j)*b[j];
            stk[++tt] = j;
        }
        for(int j=1;j<=m;j++)
            res = max(res,l[j]+r[j]-b[j]);
    }
    cout<<res*3;
    
    return 0;
}

ll qmi(ll a,ll b) {ll res=1;a%=mod;  for(;b;b>>=1){if(b&1)res=res*a%mod;a=a*a%mod;}return res;}
inline ll read(){ll s=0,w=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();return s*w;}
ll gcd(ll a,ll b){
	return b==0?a:gcd(b,a%b);
}

acwing例题3:构造数组
容易发现,这道题让我们构造一个序列,其峰值不多于一个
在这里插入图片描述
用l[i] 数组表示以第i个数为峰值,其左边的数所能形成的最大值,r[i] 数组表示以第i个数为峰值,其右边的数所能形成的最大值。
下面以计算l[i]为例:
回想单调栈的功能:找到一个数左边第一个比他小的数
若i左边第一个比它小的数字为 k 则 l[i] = l[k]+ (i-l)*m[i] ,于是我们又能用单调栈计算了。

计算出l和r数组后,由于两端是独立的,所以直接取最大值即可。

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 500010;
typedef long long ll;

ll m[N],l[N],r[N],res[N];
ll stk[N],tt = 0;;
int main()
{
    int n;cin>>n;
    for(int i=1;i<=n;i++)
        scanf("%lld",&m[i]);
    for(int i=1;i<=n;i++)
    {
        while(tt&&m[stk[tt]]>=m[i])
            --tt;
        l[i] = l[stk[tt]]+(i-stk[tt])*m[i];
        stk[++tt] = i;
    }
    tt = 0;
    stk[++tt] = n+1;
    for(int i=n;i>=1;i--)
    {
        while(tt&&m[stk[tt]]>=m[i])
            --tt;
        r[i] = r[stk[tt]]+(stk[tt]-i)*m[i];
        stk[++tt] = i;
    }
    ll sum = 0;
    int k = 0;
    for(int i=1;i<=n;i++)
    {
        if(l[i-1]+r[i]>sum)
        {
            sum = l[i-1]+r[i];
            k = i;
        }
    }
    res[k] = m[k];
    for(int i=k-1;i>=1;i--)
        res[i] = min(m[i],res[i+1]);
    for(int i=k+1;i<=n;i++)
        res[i] = min(m[i],res[i-1]);
    for(int i=1;i<=n;i++)
        printf("%lld ",res[i]);
    return 0;
}
  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值