单调栈详解和入门例题解析

【单调栈】

基本过程:

让我们来模拟一个递增的单调栈的实现过程,以序列{7, 2, 5, 3, 11, 9}为例。

主要步骤如下:若栈为空或者栈顶元素小于当前元素则压入,否则弹出栈内比当前元素大的所有元素。

第一步:栈为空,压入7。此时栈内:7 。

第二步:7比2大,弹出7,压入2。此时栈内:2 。

第三步:2比5小,压入5。此时栈内:2 5 。

第四步:5比3大,弹出5,2比3小,压入3。此时栈内:2 3 。

第五步:3比11小,压入11 。此时栈内:2 3 11 。

第六步:11比9大,弹出11,3比9小,压入9 。此时栈内:2 3 9 。

可以在纸上模拟一下这个过程。在这个过程中我们可以跑出以每个元素作为最小值的最左端点下标。

【实现代码】

#include <bits/stdc++.h>
using namespace std;

int main()
{
    int a[]={0,7,2,5,3,11,9};
    int l[10];
    stack <int> stk;
    for(int i=1;i<=6;i++){
        while(!stk.empty()&&a[stk.top()]>=a[i]) stk.pop();
        if(stk.empty()) l[i]=1;
        else l[i]=stk.top()+1;
        stk.push(i);
    }
    for(int i=1;i<=6;i++)
        printf(i<6?"%d ":"%d\n",l[i]);
}

跑出来的结果是1,1,3,3,5,5

同理,我们可以跑出每个元素作为最小值的最右端点的下标,只要从右往左跑就可以了。还有用递减的单调栈跑出每个元素作为最大值的区间的下标。可以自己尝试写一下。时间复杂度为O(n).

【单调栈基础应用】

1.给定一组数,跑出每个数和他右边第一个比他大(小)的数之间有多少个数。

2.给定一序列,寻找某一子序列使得子序列中的最小值乘以子序列的长度最长。

3.给定一序列,寻找某一子序列使得子序列中的最小值乘以子序列所有元素和最大。

【例题讲解】

EX1:POJ3250

Bad Hair Day

【题解】

题意:每头牛面朝右边,只能看到比他矮的牛,询问所有的牛能看到的牛的数目的期望。

思路:从前往后跑,每次弹出小于等于当前身高的元素,因为如果不高于当前身高就没办法越过这个位置看到后面的牛。然后每次加上当前栈内元素个数,即能看到这个位置的前面的牛的数目即可。

【代码】

#include <cstdio>
#include <stack>
using namespace std;
#define ll long long
int main()
{
    int n;
    while(~scanf("%d",&n)){
        ll sum=0;
        stack <int> stk;
        for(int i=0;i<n;i++){
            int x; scanf("%d",&x);
            while(!stk.empty()&&stk.top()<=x) stk.pop();
            sum+=stk.size();
            stk.push(x);
        }
        printf("%lld\n",sum);
    }
}

EX2:POJ2559

Largest Rectangle in a Histogram

【题解】

题意:给定一个直方图,每一个矩形宽度为1,第i个矩形高度为hi,问这样一个直方图形成的最大矩形面积是多少?

思路:跑出以每个矩形高度为最小值的左右区间,区间长度即为固定高度的最大宽度。更新答案即可。

【代码】

#include <cstdio>
#include <stack>
using namespace std;
#define ll long long
const int maxn=1e5+10;
int l[maxn],r[maxn];
ll a[maxn];
int main()
{
    int n;
    while(~scanf("%d",&n)&&n){
        stack <int> stk;
        for(int i=1;i<=n;i++){
            scanf("%lld",&a[i]);
            while(!stk.empty()&&a[stk.top()]>=a[i]) stk.pop();
            if(stk.empty()) l[i]=1;
            else l[i]=stk.top()+1;
            stk.push(i);
        }
        while(!stk.empty()) stk.pop();
        for(int i=n;i>0;i--){
            while(!stk.empty()&&a[stk.top()]>=a[i]) stk.pop();
            if(stk.empty()) r[i]=n;
            else r[i]=stk.top()-1;
            stk.push(i);
        }
        ll ans=0;
        for(int i=1;i<=n;i++)
            ans=max(ans,a[i]*(r[i]-l[i]+1));
        printf("%lld\n",ans);
    }
}

EX3:POJ2796

Feel Good

【题解】

题意:给定一个长度为n的序列,输出区间最小值乘区间和最大的一个区间的值和左右端点。

思路:跑出以每个位置为最小值的左右区间,用前缀和维护区间和,更新最大答案即可。

【代码】

#include <cstdio>
#include <stack>
using namespace std;
#define ll long long
const int maxn=1e5+10;
ll a[maxn],sum[maxn];
int l[maxn],r[maxn];

int main()
{
    int n;
    while(~scanf("%d",&n)){
        for(int i=1;i<=n;i++)
            scanf("%lld",&a[i]),sum[i]=sum[i-1]+a[i];
        stack <int> stk;
        for(int i=1;i<=n;i++){
            while(!stk.empty()&&a[stk.top()]>=a[i]) stk.pop();
            if(stk.empty()) l[i]=1;
            else l[i]=stk.top()+1;
            stk.push(i);
        }
        while(!stk.empty()) stk.pop();
        for(int i=n;i>=1;i--){
            while(!stk.empty()&&a[stk.top()]>=a[i]) stk.pop();
            if(stk.empty()) r[i]=n;
            else r[i]=stk.top()-1;
            stk.push(i);
        }
        ll ans=-1,ret,index;
        for(int i=1;i<=n;i++){
            ret=a[i]*(sum[r[i]]-sum[l[i]-1]);
            if(ans<ret){
                index=i;
                ans=ret;
            }
        }
        printf("%lld\n%d %d\n",ans,l[index],r[index]);
    }
    return 0;
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值