19015 接雨水-单调栈

Description
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,
假定我们可以向任意柱子间灌水,可以得到多个存水区域,求出其中最大的存水区域。

在这里插入图片描述
上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,
在这种情况下(蓝色部分表示存水区),最大的存水区为4号柱子至8号柱子,可存水4个单位。

输入格式
第一行一个整数n代表柱子个数。1<=n<=1e5
第二行n个非负整数,代表柱子的高度。

输出格式
输出最大的存水区域。

输入样例
12
0 1 0 2 1 0 1 3 2 1 2 1

输出样例
4

此处多给出一个测试样例:

输入样例:
100
10  7  1  0  2  2  0  1  2  8 10  6  5  1  9  8  7  4  9  5  6  7 10  3  4  5 10  5  0 10  6  8 10  8  8  2  1  3  7 10  0  3  3  4  2  9  1  6  2  4  4  0  8  5  2  2  3  2  7  5  9  9  1  5  3  6  4  0  6  8  7  5  9  6  0  4  6  5  6  3  7  4  0  7 10  8  2  8  6  4  0 10  5  5 10  3  3  4  4  9

输出样例:
248

单调栈写法一:

①、解题思路:

思路:
利用单调递减栈求左右两边第一个高于或等于当前矩形的矩形下标,用两个数组L[i] R[i]记录每个位置左右两边对应的第一个高于等于当前矩形的矩形下标
利用前缀和预处理,结果为: 左右两个边界围成的矩形面积 - 左右边界中间的矩形的高度和(利用前缀和快速计算)

②、代码:

#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;

int num[100005] = { 0 };
long long sum[100005] = { 0 };
int s[100005] = { 0 }, top = 0;
int L[100005] = { 0 }, R[100005] = { 0 };

int main()
{
    int n, i;
    cin >> n;
    int a = 0, b = 0, c = 0;
    for (i = 1; i <= n; i++)
    {
        scanf("%d", &num[i]);
        sum[i] = sum[i - 1] + num[i];//预处理,计算前缀和
    }

    //单调递减栈存储当前位置左边第一个高于或等于当前矩形的下标
    for (i = 1; i <= n; i++)
    {
        while (top > 0 && num[s[top - 1]] < num[i])//单调递减栈,如果栈顶所代表的的矩形高度小于当前矩形,那么一直出栈,最后栈的栈顶元素一定是矩形高度大于或等于当前矩形的下标
            top--;
        if (top > 0)//如果当前矩形左边有高于当前矩形的矩形
            L[i] = s[top - 1];//记录左边第一个高于或等于当前矩形的下标
        s[top++] = i;//当前下标入栈
    }

    //单调递减栈存储当前位置右边第一个高于或等于当前矩形的下标
    top = 0;
    for (i = n; i >= 1; i--)
    {
        while (top > 0 && num[s[top - 1]] < num[i])//单调递减栈,如果栈顶所代表的的矩形高度小于当前矩形,那么一直出栈,最后栈的栈顶元素一定是矩形高度大于或等于当前矩形的下标
            top--;
        if (top > 0)//如果当前矩形右边有高于当前矩形的矩形
            R[i] = s[top - 1];//记录右边第一个高于或等于当前矩形的下标
        s[top++] = i;//当前下标入栈
    }

    long long ans = 0;
    for (i = 1; i <= n; i++)
    {
        //单调栈中存储的一定是高于等于当前矩形高度的矩形下标
        if (L[i] && R[i] && num[L[i]] > num[i] && num[R[i]] > num[i])//如果当前矩形的左右两边都高于当前矩形
            ans = max(ans, 1LL * min(num[R[i]], num[L[i]]) * (R[i] - L[i] - 1) - (sum[R[i] - 1] - sum[L[i]]));//计算左右两边矩形围成的区域,减去中间的矩形高度总和
        else//如果左右边界的矩形之一的高度是刚好等于当前矩形的
        {
            long long left = 0, right = 0;
            if (L[i])//如果当前矩形的左边矩形高度等于当前矩形,这个时候L[i]不为0
                left = 1LL * num[i] * (i- L[i] - 1) - (sum[i-1] - sum[L[i]]);
            if (R[i])//如果当前矩形的右边矩形高度等于当前矩形,这个时候R[i]不为0
                right = 1LL *  num[i] * (R[i] - i - 1) - (sum[R[i] - 1] - sum[i]);
            ans = max(ans,max(left, right));//取当前矩形左或右边围成的区域跟ans比较,较大的作为结果
        }
    }
    cout << ans << endl;
    return 0;
}

单调栈写法二:

①、解题思路:

思路:
单调递减栈,出栈结束后直接计算围成的面积,代码简便
枚举从左往右遍历每一个矩形的左边能够围成的面积,记录ans,再从右往左遍历每一个矩形右边能够围成的面积,记录ans

②、代码:

#include <cstdio>
#include <iostream>

using namespace std;

int num[100005] = {0};
long long sum[100005] = {0};
int s[100005] = {0}, top = 0;
int L[100005] = {0}, R[100005] = {0};

int main()
{
    int n, i;
    cin>>n;
    int a = 0, b = 0, c = 0;
    for (i = 1; i <= n; i++)
    {
        scanf("%d",&num[i]);
        sum[i] = sum[i-1] + num[i];
    }
    long long ans = 0;
    //单调递减栈存储当前位置左边第一个高于当前矩形的下标
    for(i = 1; i <= n; i++)
    {
        while (top>0 && num[s[top-1]] < num[i])
            top--;
        if (top>0)//如果当前矩形左边有高于当前矩形的,即左边有能够围成面积的,则计算左边的面积
        {
            ans = max(ans,1LL*num[i]*(i-s[top-1]-1)-(sum[i-1]-sum[s[top-1]]));
          //  printf("i=%d  ans=%3d num[%d]=%d 宽度=%d sum[%d-1]==%lld - sum[%d]==%lld\n",i,ans,i,num[i],i-s[top-1]-1, i,sum[i-1], s[top-1],sum[s[top-1]]);
        }
        s[top++] = i;
    }
     //单调递减栈存储当前位置右边第一个高于当前矩形的下标
    top = 0;
    for(i = n; i >= 1; i--)
    {
        while (top >0 && num[ s[top-1] ] < num[i])
            top--;
        if (top > 0)//如果当前矩形右边有高于当前矩形的,即右边有能够围成面积的,则计算右边的面积
            ans = max(ans,1LL*num[i]*(s[top-1]-i-1)-(sum[s[top-1]-1]- sum[i]));
        s[top++] = i;
    }

    cout<<ans<<endl;
   return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值