数据结构:单调栈

单调栈学习笔记

单调栈,就是一个栈,使里面的元素单调递增或递减。还是从具体的例题中看一下单调栈的应用吧。

例1:Largest Rectangle in a Histogram

英文题面:Largest Rectangle in a Histogram

一句话题意:如下图所示,在一条水平线上有许多个宽一样的矩形,求包含于这些矩形内部最大的矩形的面积(就是下图中的阴影部分的面积)。矩形的个数 ≤ 1 0 5 \le 10^5 105

首先我们考虑一下,如果有 n n n 个矩形,如果矩形的高度为 h i h_i hi,且都是单调递增的话,那么就以这个矩形的高度为最终的高度,答案显而易见就是:
max ⁡ { h i × ( n − i + 1 ) }   ( i ∈ [ 1 , n ] ) . \max\{h_i \times (n-i+1) \} \ (i \in [1,n]). max{hi×(ni+1)} (i[1,n]).
但是这题中的矩形肯定不是单调递增的。但是如果按照上面的思想的话,想利用之前的矩形,以当前这个矩形的高度构成最终矩形的高度的话,那么之前所有比当前矩形高的矩形都没用了。(就是下图中红框框起来的部分)。

既然那一部分多余的都没有用处了,那我们就可以将这几个矩形用一个高为当前矩形的高,宽为几个矩形累加的新矩形来代替。那么不就和上面的简化版一样了吗?

具体来说,这题可以用单调栈来实现,时间复杂度为 O ( n ) O(n) O(n) 。我们建立一个栈,使栈中的矩形高度单调递增:

  1. 如果当前矩形高度大于栈顶矩形的高度,直接入栈。
  2. 如果当前的矩形高度小于等于栈顶矩形的高度,就不断弹出栈顶矩形,直至满足条件1,然后在栈顶放入一个宽度为弹出矩形累加的矩形。
  3. 当所有的矩形全部入栈完毕,一个一个弹出更新答案。为了方便,可以在所有矩形后加入一个高为 0 0 0 的矩形,即令 h [ n + 1 ] = 0 h[n+1] = 0 h[n+1]=0

核心代码如下

a[n+1]=top=0;
for(int i=1;i<=n+1;++i)
{
    if(a[i]>s[top]) s[++top]=a[i], w[top]=1;
    else
    {
        int width=0;
        while(s[top]>a[i])
        {
            width+=w[top];
            ans=max(ans,width*s[top]);
            top--;
        }
        s[++top]=a[i], w[top]=width+1;
    }
}

例2:City Game

查看题面:City Game

一句话题意:给出一个矩阵,求出这个矩阵中全以 F 构成的最大矩阵的面积。

如果只有一行的话,我们可以将 F 看成是高度为 1 1 1 的矩阵,将 R 看成是高度为 0 0 0 的矩阵。那么就和上面一道题一样了。

如果有两行的话,我们想一想,如果第一行为 F,但是第二行为 R,无法和第一行一起构成矩阵,那么这一列的高度也就清零了;但是如果第一行为 F,第二行同样也为 F 的话,这一列的高度就加 1 1 1

拓展到一个矩阵的话,就是下面这个表格的情况:

前一行后一行高度
FR 0 0 0
FF h [ p r e ] + 1 h[pre]+1 h[pre]+1

那我们每一行都求一次单调栈,每一行都更新一次答案就可以了。代码如下

#include <bits/stdc++.h>
#define int long long
#define M 1010
using namespace std;

int n, m, ans=0;
int f[M][M], a[M], w[M], s[M], top=0;

inline int read()
{
    int re=0, f=1; char ch=getchar();
    while(ch<'0' || ch>'9') {if(ch=='-') f=-1; ch=getchar();}
    while(ch>='0' && ch<='9') {re=re*10+(ch-'0'); ch=getchar();}
    return re*f;
}

signed main()
{
    n=read(), m=read();
    for(int i=1;i<=n;++i)
    {
        for(int j=1;j<=m;++j)
        {
            char c;
            cin>>c;
            if(c=='F') f[i][j]=1;
        }
    }    
    ans=0;
    for(int i=1;i<=n;++i)
    {
        for(int j=1;j<=m;++j)
        {
            if(f[i][j]) a[j]=a[j]+1;
            else a[j]=0;
        }
        a[m+1]=top=0;
        for(int j=1;j<=m+1;++j)
        {
            if(a[j]>s[top]) s[++top]=a[j], w[top]=1;
            else
            {
                int width=0;
                while(s[top]>a[j])
                {
                    width+=w[top];
                    ans=max(ans,width*s[top]);
                    top--;
                }
                s[++top]=a[j], w[top]=width+1;
            }
        }
    }
    printf("%lld\n",ans*3);
    return 0;
}

习题

  1. P2659 美丽的序列
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值