单调栈
所谓单调栈即一个具有单调性(呈递增或者递减状态)的一个栈(先进后出),一般用于一个求最近的最小或最大值中,能发挥奇效。
注意能使用单调栈题目一般都可以用暴力解决,且具有一定单调性质。
下面来一个简单单调栈题目:
直方图中最大的矩形
直方图是由在公共基线处对齐的一系列矩形组成的多边形。矩形具有相等的宽度,但可以具有不同的高度。
例如,图例左侧显示了由高度为2,1,4,5,1,3,3的矩形组成的直方图,矩形的宽度都为1:通常,直方图用于表示离散分布,例如,文本中字符的频率。现在,请你计算在公共基线处对齐的直方图中最大矩形的面积。图例右图显示了所描绘直方图的最大对齐矩形。
输入格式输入包含几个测试用例。每个测试用例占据一行,用以描述一个直方图,并以整数n开始,表示组成直方图的矩形数目。然后跟随n个整数h1,…,hn。这些数字以从左到右的顺序表示直方图的各个矩形的高度。每个矩形的宽度为1。同行数字用空格隔开。当输入用例为n=0时,结束输入,且该用例不用考虑。
输出格式对于每一个测试用例,输出一个整数,代表指定直方图中最大矩形的区域面积。每个数据占一行。请注意,此矩形必须在公共基线处对齐。
数据范围1≤n≤100000,0≤hi≤1000000000
我们先来理解一下题目:
- 首先里面所有的长条都是同一个底,只有高可能不一样;
- 所以我们可以根据他的高(即长度)去探索,我们发现每一条长条所能延伸成的最大矩形面积必定为他的 左边第一个小于他高度 ( left( 满足此条件的长条位置 ) ) 和他 右边第一个小于他高度 ( right ) 的矩形的距离乘上他的高度(h),即 Smax_h[ i ] = ( right -left )*h[ i ];
- 如果直接用暴力会很麻烦,所以我们根据题目不难发现,我要找到左边最近的第一个小于此长条的长条位置,就是根据单调栈的性质来的。
- 具体怎么操作下面看代码:
#include<stdio.h>
int h[100010];
int right[100010];
int p[100010];
main()
{
int n,i,j,k,r;
long long max,left;//注意left为long long下面会给出解释。
while(~scanf("%d",&n)&&n!=0)
{
for(i=0;i<n;i++)
{
scanf("%d",&h[i]);
}
max=h[0];
r=-1;
for(j=n-1;j>=0;j--)
{
while(r>=0&&h[p[r]]>=h[j])r--;/*当栈里面的数据大于等于
此长条的高,则出栈。当栈为空或者小于则跳出*/
if(r<0)right[j]=n;/*当栈为空,说明此长条往右没有满足条件
的长条。则只能为最右边的长条。*/
else right[j]=p[r];//将满足条件的长条位置放入数组中。
r++;
p[r]=j;//入栈
}
k=-1;
for(i=0;i<n;i++)
{
while(k>=0&&h[p[k]]>=h[i])k--;//同上执行左边操作。
if(k<0)left=-1;/*因为右边的数据已经存了,所以左边的数据只
需用一次,所以不用开辟数组。*/
else left=p[k];//同上
k++;
p[k]=i;
if((right[i]-left-1)*h[i]>max)max=(right[i]-left-1)*h[i];
//应为max可能会超出int范围,所以将left改为longlong 防止溢出
}
printf("%lld\n",max);
}
}
注意为什么当栈为空时,right和left要赋值为n和-1。因为我们每次取的长度是满足条件的长条的上一个(right)或者下一个(left)因为我们不能取比此长条短的长条,但我们写的终止条件是在第一个比自己高的小的时候停止,所以他的上一个或者下一个必定不会短于此长条高度。