《算法竞赛进阶指南》0x11栈 例题Largest Rectangle in a Histogram

Largest Rectangle in a Histogram

知识点:单调栈(先自己看一下蓝书P54)

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+10;
int n,tt;
ll stk[N],h[N],l[N],r[N];
int main()
{
    
    
    while(cin>>n)
    {
    
    
        if(n==0)break;
        memset(l,0,sizeof l);
        memset(r,0,sizeof r);
        for(int i=1;i<=n;i++)cin>>h[i];
        h[0]=h[n+1]=-1;//帮助清空栈
        tt=-1;
        stk[++tt]=0;
        for(int i=1;i<=n;i++)
        {
    
    
            while(h[stk[tt]]>=h[i])tt--;
            l[i]=i-stk[tt];
            stk[++tt]=i;
        }
        tt=-1;
        stk[++tt]=n+1;
        for(int i=n;i>=1;i--)
        {
    
    
            while(h[stk[tt]]>=h[i])tt--;
            r[i]=stk[tt]-i;
            stk[++tt]=i;
        }
        ll ans=0;
        for(int i=1;i<=n;i++)
        {
    
    
            ans=max(ans,h[i]*(r[i]+l[i]-1));
        }
        cout<<ans<<endl;
    }
    return 0;
}

分析:

暴力做法的思路:

把每个矩形的高h[i]作为最终矩形的高,然后像两边询问直到比该矩形高度低(即无法再以h[i]作为高扩展)

单调栈优化

我们只需知道以第i块矩形为中心向左边询问得到的第一块比h[i]小的矩形编号和向右边询问得到的第一块比h[i]小的矩形编号就可以确定最终矩形的宽度。对于中间的那些矩形,我们可以直接忽视,换个说法我们只需维护一个单调序列即可

当我们从D点向左询问时,B是第一个小于D的,所以我们可以把C剔除(也就是维护A-B-D单调递增)
在这里插入图片描述
当我们向右从F点向右询问时,J点是第一个小于F的,所以GH可以剔除,我们维护J-F单调递增区间,同理当我们从D点开始向右询问时,我们要维护J-F-D单调递增序列(也就是我们的单调栈)
在这里插入图片描述

对于栈而言,我们看重的信息为他的栈顶。以D点开始向右询问,此时我们单调栈中栈顶为F,那么我们可以知道,D这个矩形向右衍生到F就不能扩展了。

实现步骤

以向左询问为例子

1. 确定我们要什么:
要第一个比h[i]矮的矩形编号
2. 我们从哪里获取这个编号:
从单调栈的栈顶
3. 我们要维护一个怎么样的单调栈,为什么:
维护一个单调递增的单调栈,因为这样我们保证了要加入栈的(即当前发起询问的第i号矩形)比栈顶的矩形高,也就是说,在加入前,这个单调栈的栈顶就是我们想要的值
4. 如何去维护一个单调递增栈:
如果当前矩形比栈顶矩形高,直接进栈
否则不断取出栈顶直到栈为空或者栈顶矩形高度比当前矩形小

在这里插入图片描述
向右询问同理,还是维护一个单调递增栈,反向遍历即可

一些杂想

在写“第一个比当前值小的编号”这类话时候自然而然想到了二分,那么单调栈的单调性或许也可以抽象一下变成满足某些条件和不满足某些条件,那么二者的区别又是什么呢?

猜你喜欢

转载自blog.csdn.net/qq_39354847/article/details/112963451