单调栈习题

例题:

Largest Rectangle in a Histogram

题意:找出柱状图内最大矩形块,求最大面积

解析:

用单调栈处理,找不小于该点的最左端和最右端

ans=max(ans,(r-l+1)*h[i]);

ac:

#include<bits/stdc++.h>
#define ll long long
#define MAXN 100005
using namespace std;
stack<int> sk;
int a[MAXN];
int aa[MAXN];
int bb[MAXN];

int main()
{
    int n,x,y;
    while(scanf("%d",&n)&&n)
    {
        for(int i=1;i<=n;i++)
            scanf("%d",&a[i]);
        for(int i=1;i<=n;i++)
        {
            while(sk.size()&&a[sk.top()]>=a[i])
                sk.pop();
            if(sk.empty())
                aa[i]=1;
            else
                aa[i]=sk.top()+1;//找相等的点,即是第一个小于的点+1
            sk.push(i);
        }
        while(!sk.empty())
            sk.pop();
        for(int i=n;i>=1;i--)
        {
            while(sk.size()&&a[sk.top()]>=a[i])
                sk.pop();
            if(sk.empty())
                bb[i]=n;
            else
                bb[i]=sk.top()-1;//同上
            sk.push(i);
        }
        while(!sk.empty())
            sk.pop();
        ll ans=0;//注意int溢出
        for(int i=1;i<=n;i++)
            ans=max(ans,(ll)(bb[i]-aa[i]+1)*a[i]);
        printf("%lld\n",ans);
    }
    return 0;
}

https://ac.nowcoder.com/acm/contest/881/A

题意:

给定2个数组,1<=l<=r<=m,找到最大的m,对于a数组和b数组在区间(1,m)中的任意(l,r)区间内的最小值下标相同

解析:

直接从左往右遍历,找m点往左的第一个比m小的点,如果a数组和b数组相同ans++,否则跳出

这里单调栈虽然仅仅找的是第一个比a[m]小的,但是最小是可以通过若干次传递的,我们是从左往右,所以满足单调传递性

L[a]=0,L[b]=a,L[c]=b,L[d]=c;=>区间(1,d)中最小的值坐标为a.

m前面的都是相等的,所以可以传递

ac:

#include<bits/stdc++.h>
#define ll long long
#define MAXN 100005
using namespace std;
int a[MAXN],b[MAXN];
int l[MAXN];
int L[MAXN];

void solve(int n)
{
    stack<int> sk;
    for(int i=1;i<=n;i++)
    {
        while(sk.size()&&a[sk.top()]>=a[i])
            sk.pop();
        if(sk.empty())
            l[i]=0;
        else
            l[i]=sk.top();
        sk.push(i);
    }
    while(!sk.empty())
        sk.pop();
    for(int i=1;i<=n;i++)
    {
        while(sk.size()&&b[sk.top()]>=b[i])
            sk.pop();
        if(sk.empty())
            L[i]=0;
        else
            L[i]=sk.top();
        sk.push(i);
    }
}

int main()
{
    int n;
    while(scanf("%d",&n)!=EOF)
    {
        for(int i=1;i<=n;i++)
            scanf("%d",&a[i]);
        for(int i=1;i<=n;i++)
            scanf("%d",&b[i]);
        solve(n);
        int f=0;
        for(int i=1;i<=n;i++)
        {
            if(l[i]!=L[i])
                break;
            f++;
        }
        cout<<f<<endl;
    }
    return 0;
}

1.Max answer(单调栈,rmq)

https://nanti.jisuanke.com/t/38228

题意:

求最大的 区间和*区间最小值.(区间内有负数)

解析:

我们把整数部分和负数部分分开求

整数部分:只能是全为整数的区间,就是求矩形最大面积,经典单调栈题目

负数部分:遍历每一个负数a[i]

将区间扩至最大(扩至和最小),然后sum*a[i],贪心一下

扩区间方法:

求出前缀和,后缀和,然后查询a[i]的左边最小的,减去a[i]~a[n],比较是否大于0,判断是否需要往左边扩

查询最小值用rmq算法

总复杂度是O(nlogn)

ac:

线段树+单调栈:170ms

#include<bits/stdc++.h>
#define ls l,m,rt<<1
#define rs m+1,r,rt<<1|1
#define ll long long
#define MAXN 500005
using namespace std;
 
int a[MAXN],b[MAXN],c[MAXN];
ll sum[MAXN],sum2[MAXN];
int n;
 
struct node
{
    ll mins[MAXN<<2];
    void pushup(int rt)
    {
        mins[rt]=min(mins[rt<<1],mins[rt<<1|1]);
    }
 
    void build(int l,int r,int rt,ll v[])
    {
        if(l==r)
        {
            mins[rt]=v[l];
            return ;
        }
        int m=(l+r)>>1;
        build(ls,v);
        build(rs,v);
        pushup(rt);
    }
 
    ll query(int L,int R,int l,int r,int rt)
    {
        if(L<=l&&r<=R){
            return mins[rt];
        }
        int m=(l+r)>>1;
        ll res=1e18;
        if(L<=m)
            res=min(res,query(L,R,ls));
        if(R>m)
            res=min(res,query(L,R,rs));
        return res;
    }
}AA,BB;
 
ll solve()
{
    stack<int> sk;
    for(int i=1;i<=n;i++)
    {
        while(sk.size()&&a[sk.top()]>=a[i])
            sk.pop();
        if(sk.empty())//左边没有,呢么就是0
            b[i]=0;
        else
            b[i]=sk.top();
        sk.push(i);
    }
    while(sk.size())
        sk.pop();
    for(int i=n;i>=1;i--)
    {
        while(sk.size()&&a[sk.top()]>=a[i])
            sk.pop();
        if(sk.empty())
            c[i]=n;
        else
            c[i]=sk.top()-1;
        sk.push(i);
    }
    ll ans=0;
    for(int i=1;i<=n;i++)
        ans=max(ans,(ll)a[i]*(sum[c[i]]-sum[b[i]]));
    return ans;
}
 
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]),sum[i]=sum[i-1]+a[i];
    for(int i=n;i>=1;i--)
        sum2[i]=sum2[i+1]+a[i];
    ll ans=solve();
    AA.build(1,n,1,sum);
    BB.build(1,n,1,sum2);
    for(int i=1;i<=n;i++)
    {
        if(a[i]>=0)
            continue;
        ll aa=AA.query(i,n,1,n,1)-sum[i];
        ll bb=BB.query(1,i,1,n,1)-sum2[i];
        ans=max(ans,(ll)a[i]*(aa+bb+a[i]));
    }
    printf("%lld\n",ans);
    return 0;
}

rmq+单调栈,rmq常数太大:600ms

#include<bits/stdc++.h>
#define ls l,m,rt<<1
#define rs m+1,r,rt<<1|1
#define ll long long
#define MAXN 500005
using namespace std;
 
int a[MAXN],b[MAXN],c[MAXN];
ll sum[MAXN],sum2[MAXN];
int n;
 
ll solve()
{
    stack<int> sk;
    for(int i=1;i<=n;i++)
    {
        while(sk.size()&&a[sk.top()]>=a[i])
            sk.pop();
        if(sk.empty())//左边没有,呢么就是0
            b[i]=0;
        else
            b[i]=sk.top();
        sk.push(i);
    }
    while(sk.size())
        sk.pop();
    for(int i=n;i>=1;i--)
    {
        while(sk.size()&&a[sk.top()]>=a[i])
            sk.pop();
        if(sk.empty())
            c[i]=n;
        else
            c[i]=sk.top()-1;
        sk.push(i);
    }
    ll ans=0;
    for(int i=1;i<=n;i++)
        ans=max(ans,(ll)a[i]*(sum[c[i]]-sum[b[i]]));
    return ans;
}
 
struct node
{
    ll minn[MAXN][25];
    ll query(int a,int b)
    {
        int k = log2(b-a+1);
        return min(minn[a][k],minn[b-(1<<k)+1][k]);
    }
    void buildrmq(ll n,ll c[])
    {
        int tem = (ll)floor(log2((double)n));
        for(int i=1;i<=n;i++)
            minn[i][0]=c[i];
        for(int j=1;j<=tem;j++)
        {
            for(int i=1;i+(1<<j)-1<=n;i++)
                minn[i][j] = min(minn[i][j-1],minn[i+(1<<(j-1))][j-1]);
        }
    }
}AA,BB;
 
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]),sum[i]=sum[i-1]+a[i];
    for(int i=n;i>=1;i--)
        sum2[i]=sum2[i+1]+a[i];
    ll ans=solve();
    AA.buildrmq(n,sum);
    BB.buildrmq(n,sum2);
    for(int i=1;i<=n;i++)
    {
        if(a[i]>=0)
            continue;
        ll aa=AA.query(i,n)-sum[i];
        ll bb=BB.query(1,i)-sum2[i];
        ans=max(ans,(ll)a[i]*(aa+bb+a[i]));
    }
    printf("%lld\n",ans);
    return 0;
}
/*
6
1 -1 -4 3 -5 -4
*/

2.All-one Matrices

给定1个01矩阵,求该矩阵中的独立矩阵,独立矩阵为不被其他1个矩阵完全包含

解析:

对于每个点(i,j),求出它往上的连续的1,即高为hh[i],求i点往两边扩展,高度不小于hh[i]的最左和最右(用前缀+单调栈处理)

得到以(L[i],R[i])为底,高为hh[i]的全1矩形

判断该底下面是否是否全为1,全为1就可以继续向下扩展,这个底就不算(用前缀处理)

如果这个底算,呢么给它标记vis[L[i]][R[i]]=i,以同一底的矩阵只能存在一个,去重

ac:

#include<bits/stdc++.h>
#define ll long long
#define MAXN 3005
using namespace std;
char mm[MAXN][MAXN];
int hh[MAXN][MAXN];
int n,m;
int stk[MAXN],top;
int L[MAXN],R[MAXN];
int num[MAXN];
int vis[MAXN][MAXN];
ll ans;
 
void solve(int x)
{
    top=0;stk[++top]=0;
    for(int i=1;i<=m;i++)//最左
    {
        while(top>0&&hh[x][stk[top]]>=hh[x][i])
            top--;
        L[i]=stk[top]+1;
        stk[++top]=i;
    }
    top=0,stk[++top]=m+1;
    for(int i=m;i>=1;i--)//最右
    {
        while(top>0&&hh[x][stk[top]]>=hh[x][i])
            top--;
        R[i]=stk[top]-1;
        stk[++top]=i;
    }
    for(int i=1;i<=m;i++)//计算下一层的前缀和
        num[i]=num[i-1]+(mm[x+1][i]-'0');
    for(int i=1;i<=m;i++)
    {
        if(mm[x][i]=='0')
            continue;
        if(vis[L[i]][R[i]]==x)
            continue;
        if(num[R[i]]-num[L[i]-1]==R[i]-L[i]+1)
            continue;
        ans++;
        vis[L[i]][R[i]]=x;
    }
}
 
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        scanf("%s",mm[i]+1);
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            if(mm[i][j]=='0')
                hh[i][j]=0;
            else
                hh[i][j]=hh[i-1][j]+1;
        }
    }
    ans=0;
    for(int i=1;i<=n;i++)
        solve(i);
    printf("%lld\n",ans);
    return 0;
}

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值