初识单调栈

问题是新的解决之道的催化剂。一个看似简单的题目我写了好几个小时,实在完成不了,查了查,原来需要用到传说中的单调栈。于是,学了一些皮毛。练习了几道。

51nod 1437 迈克步
http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1437

n只熊。他们站成一排队伍,从左到右依次1n编号。第i只熊的高度是ai

一组熊指的队伍中连续的一个子段。组的大小就是熊的数目。而组的力量就是这一组熊中最小的高度。

迈克想知道对于所有的组大小为x1 ≤ x ≤ n)的,最大力量是多少。


分析:
统计所有连续子段中最小值,求最大的一个。
用单纯的两重循环铁定超时,通过观察可以发现,序列和单纯的数值升降有关系。把数字放进一个栈中,升序,遇到小的数字再一个个的弹出来,直到大于等于栈顶元素或者没有元素了。这样不断更新长度是n的连续子段的最小值ans[len]。但是最后得到的数组还不是答案,因为中间过程存在没有涉及到的len。
(开始C++超时了,果断换成纯C,过了)

#include <string.h>
#include <stdio.h>
#define N 200010
int mymax(int a,int b){
    return a>b?a:b;
}
int val[N],dex[N],left_dex[N],top; // 栈val存值,dex存下标,left_dex存第一个小于它的数的下标
int ans[N];
int main()
{
    //freopen("cin.txt","r",stdin);
    int n;
    while(~scanf("%d",&n)){
        memset(ans,0,sizeof(ans));
        scanf("%d",&val[1]);
        dex[1]=1;
        top=1;
        int temp=0,len=0;
        for(int i=2;i<=n;i++){
            scanf("%d",&temp);
            while(temp<val[top]){  //弹出
                len=i-1-left_dex[dex[top]];
                ans[len]=mymax(ans[len],val[top]);
                top--;
            }
            if(temp==val[top]){
                left_dex[i]=left_dex[dex[top]];
                dex[top]=i;
            }
            else {    //压入
                left_dex[i]=dex[top];
                val[++top]=temp;
                dex[top]=i;
            }
        }
        for(int i=1;i<=top;i++){ //剩在栈中的全部处理
            len=n-left_dex[dex[i]];
            ans[len]=mymax(ans[len],val[i]);
        }
        for(int i=n-1;i>0;i--){  //处理0
            ans[i]=mymax(ans[i],ans[i+1]);
        }
        for(int i=1;i<n;i++){
            printf("%d ",ans[i]);
        }
        printf("%d\n",ans[n]);
    }
    return 0;
}
/*
6 0 4 0 3 0 2 0 0 1
0 3 0 2
5 2 0 0 1

Process returned 0 (0x0)   execution time : 0.578 s
Press any key to continue.
*/

hdu 1506 Largest Rectangle in a Histogram
大意:求解如图的最大子矩形(小矩形的宽是1)


分析:对于每一个一定高度的小矩形,只要左右两边的矩形比它高,那么宽度就可以不断拓展,或者说当两边的矩形比它低时就不能再拓宽了。
关键点:连续子段,求解极值。单调栈
(当然,这题也能用动态规划做)

#include <iostream>
#include <cstdio>
using namespace std;
typedef long long LL;
const int N=1e5+10;
LL a[N];
int dex[N],top;
LL maxm;
int main()
{
    //freopen("cin.txt","r",stdin);
    int n;
    LL temp,s;
    while(cin>>n&&n){
        top=0;
        maxm=0;
        for(int i=1;i<=n;i++){
            scanf("%lld",&temp);
            while(top>0&&temp<a[top]){   //出栈
                s=a[top]*(i-1-dex[top-1]);
                if(maxm<s)  maxm=s;
                top--;
            }
            a[++top]=temp;  //入栈
            dex[top]=i;
        }
        for(int i=top;i>=1;i--){
            s=a[i]*(n-dex[i-1]);
            maxm=maxm>s?maxm:s;
        }
        printf("%lld\n",maxm);
    }
    return 0;
}

POJ 2796 Feel Good
大意:求解一个连续的子段,要求其和与子段中最小值的乘积最大。
分析:关键词——连续子段,极值。
单调栈。
#include <iostream>
#include <cstdio>
using namespace std;
const int N=1e5+10;
typedef long long LL;
LL a[N],dex[N],top;
LL sum[N];
LL ans,L,R;
int main()
{
    LL n,temp;
    while(cin>>n){
        top=0;
        ans=0;
        for(int i=1;i<=n;i++){
            scanf("%lld",&temp);
            sum[i]=sum[i-1]+temp;
            while(top>0&&temp<=a[top]){  //wrong -> (sum[i]-sum[dex[top]-1])*a[dex[top]]>ans
                if((sum[i-1]-sum[dex[top-1]])*a[top]>ans){
                    ans=(sum[i-1]-sum[dex[top-1]])*a[top];
                    L=dex[top-1]+1;
                    R=i-1;
                }
                top--;
            }
            dex[++top]=i;
            a[top]=temp;
        }
        while(top){
            if((sum[n]-sum[dex[top-1]])*a[top]>=ans){
                ans=(sum[n]-sum[dex[top-1]])*a[top];
                L=dex[top-1]+1;
                R=n;
            }
            top--;
        }
        printf("%I64d\n%I64d %I64d\n",ans,L,R);
    }
    return 0;
}




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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值