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;
}