zoukankan      html  css  js  c++  java
  • 单调栈练习题

    参考博客:https://blog.csdn.net/zuzhiang/article/details/78134247

    题目大部分都是模板题,代码都差不多,只是有一道题看起来没有那么明显。

    我感觉大致规律就是入栈时找到左边第一个元素位置(大于或小于当前元素值),出栈时找到右边第一个元素位置。

    如果找左右两边第一个比当前元素小的元素位置,就维护一个单调递增(确切来说是单调不递减)的单调栈。

    如果找左右两边第一个比当前元素大的元素位置,就维护一个单调递减(确切来说是单调不递增)的单调栈。

    如果找左右两边第一个小于等于当前元素的元素位置,就维护一个单调递增(严格单调递增)的单调栈。

    如果找左右两边第一个大于等于当前元素的元素位置,就维护一个单调递减(严格单调递减)的单调栈。

    其实也不用记,写单调栈时临时考虑一下就行了

    题目一:洛谷P2659   

    题目链接:https://www.luogu.org/problem/P2659

    其实就是找出每一个元素左右两边第一个小于它的元素位置。

    因为是左右两边第一个小于它的元素位置,所以我们就维护一个单调不递减的单调栈。

    从栈底到栈顶的数字是依次增大的,在求的过程中要始终保持递增(不递减)的性质。我们每次在数字入栈时求出数字左边第一个小于它的数字位置,在出栈的时候求出右边第一个小于它的数字位置,为什么是这样,看下面,我这里的栈保存的是元素下标,而不是元素值。

    L[i]记录左边第一个小于a[i]的元素位置(下标),没有就是0,R[i]记录右边第一个小于a[i]的元素位置,没有就是n+1。

      对于一个a[i],假如a[i]大于栈顶元素(我的栈存的是下标,我们比较是就拿这个下标的值比较),那么如果把a[i]压入栈顶,这个栈还是递增的,这个时候栈顶元素就是第一个小于a[i]的数字,我们直接把L[i]赋值为栈顶元素就行了,然后让 i (下标)入栈。

      对于a[i]等于栈顶元素的情况,假如说值都是5好吧,那么其实栈顶的5在它入栈的时候,它的L数组值(它左边第一个小于它的元素位置)我们一定会先得到对吧,那么当前的a[i](也是5),它的L数组值肯定等于栈顶元素的L数组值吧,因为他俩相等,所以直接把a[i]的L数组值赋值为栈顶元素的L数组值就可以了,再把 i 入栈。

      假如a[i]小于栈顶元素,那么如果我们把a[i]压入栈中,那么这个栈里面的元素就不单调递增了,所以说我们需要把栈里面比a[i]大的元素都弹出,假如现在我们要弹出当前栈顶元素,那么其实现在a[i]就是栈顶元素右边的第一个小于栈顶元素的值,所以我们这个时候可以直接把R[s.top()]赋值为i (出栈时得到右边第一个小于栈顶元素的元素位置),这样每当我们出栈一个元素,这个元素的左右两边第一个小于它的元素位置就都找到了(左边的在进栈时就找到了)。

    代码(我的栈都不是手写栈,比较菜):

    #include<iostream>
    #include<cstring>
    #include<algorithm>
    #include<queue>
    #include<map>
    #include<stack>
    #include<cmath>
    #include<vector>
    #include<set>
    #include<cstdio>
    #include<string>
    #include<deque> 
    using namespace std;
    typedef long long LL;
    #define eps 1e-8
    #define INF 0x3f3f3f3f
    #define maxn 2000005
    stack<int>s;
    int n,m,k,t;
    int a[maxn];
    int L[maxn],R[maxn]; 
    inline int read(){
        int f=1,x=0;char ch;
        do{ch=getchar();if(ch=='-')f=-1;}while(ch<'0'||ch>'9');
        do{x=x*10+ch-'0';ch=getchar();}while(ch>='0'&&ch<='9');
        return f*x;
    }
    int main()
    {
        n=read();
        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]){//先检查栈顶元素大于a[i]的情况    //保存一下栈顶元素值(下标) 
                R[s.top()]=i;            //得到栈顶元素右边第一个小于它的元素下标 
                s.pop();        //出栈,出栈时就已经的到了R[i],i在这里是s.top() 
            }
            
            if(s.empty())        //假如栈为空 
            L[i]=0;
            else if(a[s.top()]==a[i])    //假如栈顶元素等于a[i],那么L[i]就赋值为栈顶元素的L数组值 
            L[i]=L[s.top()];
            else                        //栈顶元素小于a[i],把a[i]入栈 
            L[i]=s.top();
            
            s.push(i);                //入栈,入栈之前已经得到了L[i] 
        }
        while(!s.empty()){//最后栈顶可能还有元素没有出栈,这个时候他们的R数组值都是n+1,因为右边没有比他们小的值了 
            R[s.top()]=n+1;
            s.pop();
        }
        LL ans=0;
        for(int i=1;i<=n;i++)
        ans=max(ans,(LL)a[i]*(R[i]-L[i]-1)); 
        printf("%lld
    ",ans);
        return 0;
    }
    View Code

    类似题目:POJ 2559、POJ2796

    题目二:poj 3250

    题目链接(poj):http://poj.org/problem?id=3250

    题目链接(vj):https://vjudge.net/problem/POJ-3250

    找每个元素右边第一个大于等于当前元素的值,假设用i表示当前元素位置,R[i]表示右边第一个大于等于a[i]的元素位置,那么开区间   (i,R[i])里面包含了R[i]-i-1个小于a[i]的元素,将这些个数全部相加输出。

    既然是右边第一个大于等于,那么我们在入栈时就直接入栈,反正不用左边的,考虑出栈,什么时候出栈呢,当即将压入的元素大于等于当前栈顶元素就出栈,此时R[s.top()]就赋值为i,也就是栈顶元素得到了右边第一个大于等于它的元素位置,就这样一直出栈,直到栈为空或者栈顶元素大于即将要入栈的元素。考虑到这一点,我们就知道我们要维护的是一个严格单调递减的单调栈了。

    代码:

    #include<iostream>
    #include<cstring>
    #include<algorithm>
    #include<queue>
    #include<map>
    #include<stack>
    #include<cmath>
    #include<vector>
    #include<set>
    #include<cstdio>
    #include<string>
    #include<deque> 
    using namespace std;
    typedef long long LL;
    #define eps 1e-8
    #define INF 0x3f3f3f3f
    #define maxn 80005
    int a[maxn];
    int n,m,k,t;
    LL ans; 
    int R[maxn];//a[i]右边第一个大于它的元素位置 
    stack<int>s;
    int main()
    {
        while(scanf("%d",&n)!=EOF){
            while(!s.empty())
            s.pop();
            for(int i=1;i<=n;i++)
            scanf("%d",&a[i]);
            for(int i=1;i<=n;i++){//维护一个严格单调递减的单调栈 
                while(!s.empty()&&a[s.top()]<=a[i]){//这里是 a[s.top()]<=a[i]了 
                    R[s.top()]=i;
                    s.pop(); 
                }
                s.push(i);
            }
            while(!s.empty()){
                R[s.top()]=n+1;
                s.pop(); 
            }
            ans=0;
            for(int i=1;i<=n;i++)
            ans+=R[i]-i-1;
            printf("%lld
    ",ans);
        }
        return 0;
    }
    View Code

    题目三:POJ3494

    题目链接(POJ):https://vjudge.net/problem/16256/origin

    题目链接(vj):https://vjudge.net/problem/POJ-3494

    题意:在给出的矩阵里面找一个最大的子矩阵(里面的元素个数最多),要求子矩阵里面元素都是1,输出这个子矩阵里面的元素1的个数。

    这个乍一看不知道还真的不知道要用单调栈,但是既然你都说了用单调栈,强行联想还是很容易想出来的,就是维护2000个单调栈,对于矩阵的每一列(当然,每一行也可以),我们都把它看成一个单调栈。

    比如说矩阵 :

    0 1 1 0
    1 1 1 0
    1 1 1 1
    0 1 1 0

    我们处理一下就得到了如下矩阵:

    0 1 2 0
    1 2 3 0
    1 2 3 4
    0 1 2 0

    这里有4列,分别是0 1 1 0、1 2 2 1、2 3 3 2、0 0 4 0

    a[i][j]这个时候就表示在第i行第j列位置前面(包含a[i][j])有多少个连续的数字1。这是左右方向上的连续值,然后我们就要求竖直方向上连续的值了。

    对于每一列,我们都找到他上下方向(竖直方向)上第一个小于当前元素的元素位置,然后取a[i][j]*(R[j][i]-L[j][i]-1)中的最大值就行了,这里j代表列,R[j][i]就代表第j列的第i个元素下面第一个小于a[i][j]的元素位置。可能比较绕。

    代码(我的代码不是很快):

    #include<iostream>
    #include<cstring>
    #include<algorithm>
    #include<queue>
    #include<map>
    #include<stack>
    #include<cmath>
    #include<vector>
    #include<set>
    #include<cstdio>
    #include<string>
    #include<deque> 
    using namespace std;
    typedef long long LL;
    #define eps 1e-8
    #define INF 0x3f3f3f3f
    #define maxn 2005
    int a[maxn][maxn];
    int L[maxn][maxn],R[maxn][maxn];//L[i][j]代表第i列上小于a[j][i]的第一个元素位置(上下方向) 
    int n,m,k,t;
    stack<int>s[maxn];
    LL ans=0;
    int main()
    {
        while(scanf("%d%d",&n,&m)!=EOF){
            for(int i=1;i<=m;i++)
            while(!s[i].empty()) s[i].pop(); 
            for(int i=1;i<=n;i++){
                int pre=0;//对矩阵进行处理,pre代表当前j前面的连续1的个数 
                for(int j=1;j<=m;j++){
                    scanf("%d",&a[i][j]);
                    if(a[i][j])        //当前元素是1 
                    a[i][j]=++pre; 
                    else            //当前元素是0,重置pre 
                    pre=0;
                }
            }
            for(int j=1;j<=m;j++){//枚举列 
                for(int i=1;i<=n;i++){//每一列就是一个单调不递减的单调栈 ,s[j]就是第j列的栈 
                    while(!s[j].empty()&&a[s[j].top()][j]>a[i][j]){    //其实就是前面多加了一维 
                        R[j][s[j].top()]=i;
                        s[j].pop();
                    }
                    if(s[j].empty())
                    L[j][i]=0;
                    else if(a[i][j]==a[s[j].top()][j])
                    L[j][i]=L[j][s[j].top()];
                    else 
                    L[j][i]=s[j].top();        
                    s[j].push(i);
                }
                while(!s[j].empty()){
                    R[j][s[j].top()]=n+1;
                    s[j].pop();
                }
            }
            ans=0;
            for(int j=1;j<=m;j++){
                for(int i=1;i<=n;i++){
                    ans=max(ans,(LL)a[i][j]*(R[j][i]-L[j][i]-1));//找最大值就行了 
                }
            }
            printf("%lld
    ",ans);
        }
        return 0;
    }
    View Code
  • 相关阅读:
    mongodb教程
    redis高级知识
    memcached删除机制与大数据缓存问题
    nginx 运维基础
    mysql 集群与分区
    Memcached之缓存雪崩,缓存穿透,缓存预热,缓存算法
    git cz配置
    Angular零碎知识点-持续补充
    Vue学习笔记-组件
    Vue学习笔记-自定义指令生命周期函数
  • 原文地址:https://www.cnblogs.com/6262369sss/p/11294028.html
Copyright © 2011-2022 走看看