CSP_training Week5 ProblemA(HDU-1506)单调栈问题

Week5 Problem A 单调栈求解

数据结构概述

众做周知栈结构是一种很常用的数据结构,在c++的STL库中也有封装好的栈结构stack,可以使用下面的语句进行调用

#include<stack>

单调栈如其名,一种单调的栈结构,可以通过维持一个自顶向下递增(递减)数据序列,来完成目的数据的访问控制和大小判断。一般来说,单调栈主要用来解决找到某个点的全局最大&最小范围,从而进行下一步问题的求解。例题请看下文:

题目概述

题目叙述

给一个直方图,求直方图中的最大矩形的面积。例如,下面这个图片中直方图的高度从左到右分别是2, 1, 4, 5, 1, 3, 3, 他们的宽都是1,其中最大的矩形是阴影部分。
图例

INPUT & 输入样例

输入包含多组数据。每组数据用一个整数n来表示直方图中小矩形的个数,你可以假定1 <= n <= 100000. 然后接下来n个整数h1, …, hn, 满足 0 <= hi <= 1000000000. 这些数字表示直方图中从左到右每个小矩形的高度,每个小矩形的宽度为1。 测试数据以0结尾。
输入样例:

7 2 1 4 5 1 3 3
4 1000 1000 1000 1000
0

OUTPUT & 输出样例

对于每组测试数据输出一行一个整数表示答案。
输出样例

8
4000

题目重述和坑点

题意比较明确,给定一个直方图序列,求出其内部可容纳的最大的矩形大小,但是坑可不浅,所以还需仔细分析题意。

解题思路

坑一:合法矩形块的定义,浏览题目的示意图可以看出,合法的最大矩形块应该满足该矩形块左右边界两侧的直方图数值都小于他,也就是“矩形刚好被截断”的位置。
分析清楚了上面的定义,我们可以思考,图中某个x的合法最大矩形面积该怎么求?
分析一下样例结合基本数学公式可以得出如下:

S=y(x)×(x延伸上界-x延伸下界)

问题就被转换成了求出每个x的延伸上界和延伸下界,总结下即求出以x作为最小值的连续区间的上下界。转换到这里,我们就可以使用固定的方法:单调栈来求解问题。
前面提及:
单调栈可以用来求解全局范围内的,x作为最大值(最小值)的连续区间。利用单调栈的单调性质我们可以将问题分为以下三种:(以自栈底向栈顶的递增栈为例)
1、栈空–直接压栈
2、栈非空&要插入的元素大于等于栈顶元素–压栈
3、栈非空&要插入的元素小于栈顶元素–弹出栈顶元素并再次判断,直至满足条件2并压栈。在这一步重要的是另外一个操作,要将被弹出点作为最小值的延伸上限记为压栈的元素位置。
按照上面的步骤,可以得到每个点右侧的延伸上限,访问顺序倒置,可以得到每个点左侧的访问下限。剩下步骤暴力即可轻松求解。
看到这里你一定认为结束了,那么你就会像傻傻的博主一样:
爱题目中在这里插入图片描述
这又是为什么呢?
且看坑二:在题目中看到了点的取值大小最多是1e9,正常来说int是可以存下来的,但是不要忽略的是,我们计算的是面积,一单某个矩形的宽度接近1e9,长度接近1e5,就会触发第二个坑点,int不足以存下来面积大小,后面的面积也会失效,导致不明就里的wa掉。
修改方案:long long大法好,long long就完事了

总结

总的来说题目难度不大,主要考察对单调栈的模型是否熟悉,但是坑点确实容易触发,以后在看到数据取值达到1e9就要注意会不会因为运算超出范围导致求解错误了!
单调栈在很多相似问题中都有应用,只要给定的关键字有下列几个:
全局
以x为最小值的最大区间
x左边(右边)第一个大于(小于)x的值
单调栈往往能成为解决问题的好办法!

题目源码

#include<iostream>
#include<stdio.h>
#include<stack>
#include<algorithm>
using namespace std;
long long n;
long long a[100010];long long L[100010];long long R[100010];
stack<int> s;
//对于i来说
//L:i能向左边延伸的最靠左的位置 
//L[n]  等于他左边第一个比他小的位置-1
//R: i能向右边延伸的最靠右的位置
//R[n]  等于他右边第一个比他小的位置+1
/*模拟一个例子:
[3 1 2 4 0]
[3]
[3 1]
[1] R[1]=2-1
[1 2]
[1 2 4]
[1 2 4 0]
[1 2 0] R[4]=5-1=4
[1 0]  R[3]=5-1=4
[0 ] R[2]=5-1=4
R[5]=5
*/
void solve1()
{
    for(int i=1;i<=n;i++)
    {
    	if(s.empty())
    	s.push(i);
    	else
    	{
        	if(a[s.top()]<=a[i])
            s.push(i);
            else
            {
            	while(a[s.top()]>a[i])
        		{
            		R[s.top()]=i-1;
            		s.pop();
            		if(s.empty())
            		break;
        		}
        		s.push(i);
			}
    	}
    }
    while(!s.empty())
    {
        R[s.top()]=n;
        s.pop();
    }
}
void solve2()
{
    for(int i=n;i>=1;i--)
    {
    	if(s.empty())
    	s.push(i);
    	else
    	{
        	if(a[s.top()]<=a[i])
            s.push(i);
            else
            {
            	while(a[s.top()]>a[i])
        		{
            		L[s.top()]=i+1;
            		s.pop();
            		if(s.empty())
            		break;
        		}
        		s.push(i);
			}
    	}
    }
    while(!s.empty())
    {
        L[s.top()]=1;
        s.pop();
    }
}
int main()
{
	scanf("%lld",&n);
    while(n!=0)
    {
    	for(int i=1;i<=n;i++)
    {
        scanf("%lld",&a[i]);
    }  
    solve1();
    solve2();
    long long ans=0;
    for(int i=1;i<=n;i++)
	{
		ans=max(ans,a[i]*(R[i]-L[i]+1));
	 }
    printf("%lld\n",ans);
    scanf("%lld",&n);
	}
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值