zoukankan      html  css  js  c++  java
  • 单调栈

     单调栈:

    单调栈解决的是:以某个值为最小(最大)值的最大区间。

    实现方法:

    求最小值(最大值)的最大区间,维护一个递减(递增)的栈。(下面以求最小值最大区间为例)

    当遇到一个比栈顶小的值的时候开始弹栈,弹栈停止的位置到这个值的区间即为此值左边的最大区间;同时,当一个值被弹掉的时候也就意味着比它更小的值来了,也可以计算被弹掉的值的右边的最大区间。

    单调递增:数据出栈的序列为单调递增序列(即从栈顶到栈底的元素是单调递增的)

    单调递减:数据出栈的序列为单调递减序列(与上面相反)

     模板如下:

     1 stack<int> sta;
     2 for (遍历这个数组)
     3 {
     4     while(栈不为空 && 栈顶元素小于当前元素){
     5         更新结果;
     6         栈顶元素出栈;
     7     }
     8     if(栈空 || 栈顶元素大于等于当前比较元素){
     9         当前数据入栈;
    10     }
    11 }

     或

     1 stack<int> sta;
     2 for (遍历这个数组)
     3 {
     4     if(栈空 || 栈顶元素大于等于当前比较元素){
     5         入栈;
     6     }
     7     else{
     8         while(栈不为空 && 栈顶元素小于当前元素){
     9             更新结果;
    10             栈顶元素出栈;
    11         }
    12         当前数据入栈;
    13     }
    14 }
    15                             

     /*如果要使最后单调栈内的元素能全部弹出,一般要在数组最后加一个符合条件的值,使栈内元素能全部弹出。*/

    例题:Largest Rectangle in a Histogram

    题意

    求所给出的条形图可形成的最大矩形面积。

    题解

    以每一个矩形的高度作为最终大矩形的高度,看最宽能是多少,然后统计最优解。(暴力法O(n^2)超时)

    维护单调栈(单调递减),当前高度数值大于栈顶元素大小则入栈,小于则出栈。在维护单调性的弹出操作时统计宽度,不断更新答案即可得到最优解。

    即为求以每个高度为最小值的最大宽,计算形成的矩形面积,不断更新答案求最大面积值。

    Code1(用STL的stack)

    具体做法:当遇到一个比栈顶小的值的时候开始准备弹栈,此时意味着栈顶的值遇到了比它小的值,所以当前位置即为以该栈顶即将弹出的值为最小值的区间最右端;每个当前准备入栈的值的左端点初始化为当前位置,当需要弹栈时,弹栈最后停止的位置更新为当前准备入栈的值的左端点;故每弹一次栈即可求以弹栈值为最小值的最大区间。(注意得到的区间是为左闭右开的)

    #include<cstdio>
    #include<stack>
    using namespace std;
    typedef long long ll;
    const int maxn=1e5+5;
    ll h[maxn];
    int main()
    {
        ll n;
        while(~scanf("%lld",&n)&&n){
            for(int i=1;i<=n;i++){
                scanf("%lld",&h[i]);
            }
            h[n+1]=0;//为了让栈内的元素最后能全部弹出,令h[n+1]=0。因为是多测试用例,所以这里必须重新初始化,否则可能会受前一次测试用例影响
            ll area,Max=-1;
            stack<pair<ll,ll> >S;//第一个存高度值,第二个存当前高度可达到的最左端位置
            for(int i=1;i<=n+1;i++){
                ll L=i;//可达的最左端位置L初始化为当前位置
                while(!S.empty()&&h[i]<=S.top().first){ //出栈
                    L=S.top().second;//为当前准备入栈的高度值更新可达的最左端位置L
                    area=(i-L)*S.top().first;//L为矩形左边界,当前i为右边界,i-L为矩形的宽,S.top().first为矩形的高
                    Max=max(area,Max);
                    S.pop();
                }
                if(S.empty()||h[i]>S.top().first)//入栈
                    S.push({h[i],L});
            }
            printf("%lld
    ",Max);
        }
        return 0;
    }
    View Code

     Code2:手写栈

    写法不同于上面,但根源思路其实是一样的。

    /*AC 109ms*/
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    typedef long long ll;
    const int MAX=1e5+5;
    using namespace std;
    ll h[MAX],w[MAX],stack[MAX];
    int main()
    {
        int n;
        while(scanf("%d",&n)&&n)
        {
            memset(h,0,sizeof(h));//多测试用例,必须要重新初始化
            ll ans=0,k;///k为右宽
            for(int i=1;i<=n;i++){
                scanf("%lld",&h[i]);
            }
            
            h[n+1]=0;//为了方便把最后剩下的单调递增的矩形也统计进去,我们假设h[n+1]的位置有一个高度为0的矩形,最后将它加入单调栈时他会将所有矩形都弹出,那么答案也就完成最后的更新了。
            int top=0;
            stack[0]=-1;///注意这里要将单调栈的0下标位置初始化为-1,否则有可能会在下面while处陷入死循环导致超时
            
            for(int i=1;i<=n+1;i++){
                if(h[i]>stack[top]){///严格单调递减
                    stack[++top]=h[i];///入栈
                    w[top]=1;//左宽为1,即该入栈元素本身
                }
                else{
                    k=0;///第一个出栈元素右宽为0
                    while(h[i]<=stack[top]){
                        w[top]+=k;///算出该出栈元素的总宽(左宽+右宽)
                        ans=max(stack[top]*w[top],ans);///计算以该出栈元素为高的矩形面积,更新最优解
                        k=w[top];///下一个出栈元素的右宽为上一个出栈元素的总宽
                        top--;
                    }
                    stack[++top]=h[i];///入栈
                    w[top]=1+k;//该入栈元素左宽为最后一个出栈元素的总宽+1
                }
            }
            printf("%lld
    ",ans);
        }
        return 0;
    }
    View Code

    题目:Feel Good

    题意

    给出正整数n和一个(1 <= n <= 100 000)长度的数列,要求找出一个子区间,使这个子区间的数字和乘上子区间中的最小值的结果最大。输出这个最大值与区间的两个端点。

    题解

    前缀和+单调栈,和上面的题原理一样,具体看代码

    Code

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int maxn=1e5+5;
    int main()
    {
        ll n;
        while(~scanf("%lld",&n))
        {
            ll a[maxn]={},sum[maxn]={};
            for(int i=1;i<=n;i++){
                scanf("%lld",&a[i]);
                sum[i]=sum[i-1]+a[i];
            }
            a[n+1]=-1;
            stack<pair<ll,ll> >sta;
            ll ans=-1,l,r;
            for(int i=1;i<=n+1;i++){
                ll Left=i;
                while(!sta.empty()&&a[i]<=sta.top().first){
                    if((sum[i-1]-sum[sta.top().second-1])*sta.top().first>ans){
                        ans=(sum[i-1]-sum[sta.top().second-1])*sta.top().first;
                        l=sta.top().second;//记录最大结果的左右端点
                        r=i-1;
                    }
                    Left=sta.top().second;
                    sta.pop();
                }
                sta.push({a[i],Left});
            }
            printf("%lld
    ",ans);
            printf("%lld %lld
    ",l,r);
        }
        return 0;
    }
    View Code
  • 相关阅读:
    asp.net core系列 26 EF模型配置(实体关系)
    asp.net core系列 25 EF模型配置(隐藏属性)
    asp.net core系列 24 EF模型配置(主键,生成值,最大长度,并发标记)
    (办公)TOKEN
    (办公)plug-in org.eclipse.jdt.ui was unable to load class org.eclipse.jdt.internal
    (办公)系统死锁了.
    (办公)MojoExecutionException
    (生活)Photoshop入门(不定时更新)
    (办公)百度api的使用
    (办公)Mysql入门
  • 原文地址:https://www.cnblogs.com/HOLLAY/p/11354622.html
Copyright © 2011-2022 走看看