acm专题学习之单调栈(一)单调栈入门+Max answer

单调栈:

定义:栈里面保持一种单调性

举例:

  • 栈里面的元素为1,2,5,6(6是栈顶)
  • 插入一个元素7
  • 7大于栈顶元素6,可以直接入栈,此时栈为1,2,5,6,7
  • 插入一个元素4
  • 4小于栈顶元素7,7出栈,栈为1,2,5,6
  • 4小于栈顶元素6,6出栈,栈为1,2,5
  • 4小于栈顶元素5,5出栈,栈为1,2
  • 4大于栈顶元素2,可以直接入栈,此时栈为1,2,4

遇到比栈顶元素大的就直接入栈,遇到比栈顶元素小的就一直出栈到栈顶元素比它小。(在递增单调性的情况下)

Max answer(南昌网络赛)

题意:给你一个序列,让你找出一个最大和(和=子串(连续)和*子串中最小值)

思路:通过单调栈求出序列中每个数作为最小值的最大范围,例如序列3,2,5,4,7,每个数作为最小值的范围为3(0 1),2(0 5),5(3 3),4(3 5),7(5 5)。这个范围可以通过单调栈来得到。确定左边第一本比i坐标小,从左到右遍历:如果栈里面有比它大的,就出栈;如果遇到比它小的或者没有了,就停下,让该位置的入栈,此时遍历到的i位置左边第一个比它小的位置为栈顶元素的位置,如果栈顶没有元素就为0。找右边最小的也同理。

让每个位置都当一次最小值,然后乘上最小值的最大范围里面的合。(对于最小值是正数管用)

(注意)负数处理,需要用到前缀和。最小值是负数的话,最大范围*最小值不一定是最大值,比如说-5 -1 5 9(-5为选取的最小值),但是最大范围*最小值=-40,实际上(最大范围*最小值)的最大值应该是(-5-1)*(-5)=30。可以确定这个最大对应的区间一定是包括最小值且在最大范围之内。要在最大范围里面找到一个包括最小值的最小和,用枚举最小值两边不同点的组合范围,如果点比较多可能就超时了。

这里给出一个前缀和的解决办法。在最大范围里面找到一个包括最小值的最小和=(最小值,最右)中找的最小前缀合-[最左,最小值)中找的最大前缀合,只需要扫描一次。

代码:

#include <algorithm>
#include <iostream>
#include <string>
#include <vector>
#include <stack>
#include <cstdlib>
#include <map>
#include <queue>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <vector>
#include <set>
using namespace::std;
const long long int INF = 0x3f3f3f3f3f3f3f3f;
const int mod = 1000007;
const int N = 5e5 + 5;
const int M = 1e3 + 5;
int a[N], stk[N], l[N], r[N];//stk单调栈数组
long long int sum[N];
int main()
{
    int n;
    scanf("%d",&n);
    for(int i=1; i<=n; i++)
    {
        scanf("%d",&a[i]);
        sum[i] = sum[i-1] + a[i];//求前缀和
    }
    sum[n+1] = sum[n];

    int tp = 0;//用来控制单调栈
    for(int i=1; i<=n; i++)
    {
        while(tp > 0 && a[stk[tp-1]] >= a[i])//如果栈里面有比它大的,就出栈
            tp--;
        stk[tp++] = i;//让该位置的入栈
        if(tp == 1)
            l[i] = 0;
        else
            l[i] = stk[tp-2];
    }
    tp = 0;
    for(int i=n; i>0; i--)
    {
        while(tp > 0 && a[stk[tp-1]] >= a[i])
            tp--;
        stk[tp++] = i;
        if(tp == 1)
            r[i] = n+1;
        else
            r[i] = stk[tp-2];
    }

    long long int ans = -INF;
    for(int i=1; i<=n; i++)
    {
        long long int tmp, minr = INF, maxl = -INF;//前缀和
        if(a[i] < 0)
        {
            //找左边最小前缀合
            for(int j=i; j<r[i]; j++)
            {
                minr = min(minr, sum[j]);
            }
            //找右边最大前缀和
            for(int j=i-1; j>=l[i]; j--)
            {
                maxl = max(maxl, sum[j]);
            }
            tmp = (minr - maxl) * a[i];
        }
        else
            tmp = (sum[r[i]-1] - sum[l[i]]) * a[i];
        if(tmp > ans)
        {
            ans = tmp;
        }
    }
    printf("%lld", ans);
    return 0;
}

总结:1 学习了单调栈,虽然去年暑假做过类似的 2 学习了处理负数,这道题比较大的坑

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值