单调栈的应用 (直方图最大矩形 + 接雨水 + 城市游戏)

模板

常见模型:找出每个数左边离它最近的比它大/小的数
int tt = 0;
for (int i = 1; i <= n; i ++ )
{
    while (tt && check(stk[tt], i)) tt -- ;
    stk[ ++ tt] = i;
}

应用场景

找出每个数左边离它最近的比它大/小的数

例题

直方图中最大的矩形【单调栈】

题目描述

直方图是由在公共基线处对齐的一系列矩形组成的多边形。
矩形具有相等的宽度,但可以具有不同的高度。
例如,图例左侧显示了由高度为2,1,4,5,1,3,3的矩形组成的直方图,矩形的宽度都为1:
2559_1.jpg
通常,直方图用于表示离散分布,例如,文本中字符的频率。
现在,请你计算在公共基线处对齐的直方图中最大矩形的面积。
图例右图显示了所描绘直方图的最大对齐矩形。
输入格式
输入包含几个测试用例。

每个测试用例占据一行,用以描述一个直方图,并以整数n开始,表示组成直方图的矩形数目。
然后跟随n个整数h1,…,hn。
这些数字以从左到右的顺序表示直方图的各个矩形的高度。
每个矩形的宽度为1。
同行数字用空格隔开。
当输入用例为n=0时,结束输入,且该用例不用考虑。
输出格式
对于每一个测试用例,输出一个整数,代表指定直方图中最大矩形的区域面积。
每个数据占一行。
请注意,此矩形必须在公共基线处对齐。
数据范围
1≤n≤100000,
0≤hi≤1000000000
输入样例:
7 2 1 4 5 1 3 3
4 1000 1000 1000 1000
0
输出样例:
8
4000

题解

以每个矩形的高度为准,向两边扩展,直到遇到比它矮的为止.问题转化为求每一个高度左边第一个比他矮的下标和右边第一个比他矮的下标。
在这里插入图片描述
考虑单调栈优化:由图可知,由于2号元素的存在,1号元素永远不会被访问。因为2号元素比1号元素矮。
同理:当遍历到4号元素时,由于4号元素比3号元素矮,3号元素永远不会被4号之后的元素访问,因此当遍历到4号元素时,三号元素可以出栈了。

边界处理:

每个矩形的高度都是>=0的,为了使得每个矩形的两侧都有矮于它的矩形,
所以往两侧放了两个-1的矩形h[0] = h[n + 1] = -1;
由于有-1矩形的存在,不会有任何矩形的高度 hh 满足 −1>=h,所以栈不会空
因此可以省略栈中是否有元素的判断条件tt >= 0

代码

#include <iostream>

using namespace std;
typedef long long LL;
const int N = 100010;
LL h[N];
int l[N], r[N], q[N], tt = -1;

int main()
{
    int n;
    while(cin >> n, n)
    {
        h[0] = h[n + 1] = -1; //处理边界
        for(int i = 1; i <= n; i++) cin >> h[i];
        
        tt = -1;
        q[++tt] = 0;
        for(int i = 1; i <= n; i++)
        {
            while(h[q[tt]] >= h[i]) tt--;
            l[i] = i - q[tt];
            q[++tt] = i;
        }
        
        
        tt = -1;
        q[++tt] = n + 1;
        for(int i = n; i >= 1; i--)
        {
            while(h[q[tt]] >= h[i]) tt--;
            r[i] = q[tt] - i;
            q[++tt] = i;
            
        }
        
        LL res = 0;
        for(int i = 1; i <= n; i++)
        {
            res = max(res, (l[i] + r[i] - 1ll) * h[i]);
        }
        
        printf("%lld\n", res);
    }
    
    return 0;
}


接雨水

题目描述

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
例如,当给定数字序列为 0,1,0,2,1,0,1,3,2,1,2,1 时,柱子高度图如下所示,最多可以接 6 个单位的雨水。
在这里插入图片描述
输入格式
第一行包含整数 n。
第二行包含 n 个非负整数。
输出格式
输出一个整数,表示最大接水量。
数据范围
1≤n≤105,
序列中元素均不大于 1000。
输入样例:
12
0 1 0 2 1 0 1 3 2 1 2 1
输出样例:
6

题解

只有当两边高,中间低才可能积水,因此可以维护一个单调递减栈,当遇到一个大于栈顶的元素时,可以形成一个水哇。计算这个水哇的面积。栈里存储下标。当遇到一个大于栈顶的元素时,这就是水哇的右边界,弹出栈顶元素,此时栈顶元素恰好是水哇的左边界。栈顶元素对应的高度就是水哇的下边界。上边界就是左右边界最小值。

代码

#include <iostream>

using namespace std;

const int N = 100010;
int a[N], q[N], tt = -1;

int main()
{
    int n;
    cin >> n;
    for(int i = 0; i < n; i++) cin >> a[i];
    
    int res = 0;
    for(int i = 0; i < n; i++)
    {
        while(tt >= 0 && a[q[tt]] < a[i])
        {
            int t = q[tt--];
            
            if(tt >= 0)
            {
                int l = i - q[tt] - 1;
                int h = min(a[q[tt]], a[i]) - a[t];
                res += l * h;
            }
        }
        
        q[++tt] = i;
    }
    
    cout << res << endl;
    
    return 0;
}

城市游戏

题目描述

有一天,小猫rainbow和freda来到了湘西张家界的天门山玉蟾宫,玉蟾宫宫主蓝兔盛情地款待了它们,并赐予它们一片土地。
这片土地被分成NM个格子,每个格子里写着’R’或者’F’,R代表这块土地被赐予了rainbow,F代表这块土地被赐予了freda。
现在freda要在这里卖萌。。。它要找一块矩形土地,要求这片土地都标着’F’并且面积最大。
但是rainbow和freda的OI水平都弱爆了,找不出这块土地,而蓝兔也想看freda卖萌(她显然是不会编程的……),所以它们决定,如果你找到的土地面积为S,它们将给你3
S两银子。
输入格式
第一行包括两个整数N,M,表示矩形土地有N行M列。
接下来N行,每行M个用空格隔开的字符’F’或’R’,描述了矩形土地。
每行末尾没有多余空格。
输出格式
输出一个整数,表示你能得到多少银子,即(3*最大’F’矩形土地面积)的值。
数据范围
1≤N,M≤1000
输入样例:
5 6
R F F F F F
F F F F F F
R R R F F F
F F F F F F
F F F F F F
输出样例:
45

题解

从上到下统计每一行(包括自己)上每一列连续F的个数,然后就转化成直方图矩形的最大面积了。

代码

#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;

const int N = 1010;
char g[N][N];
int f[N][N];
int n, m;
int q[N], tt = -1;
int l[N], r[N];

int work(int f[])
{
    memset(l, 0, sizeof l);
    memset(r, 0, sizeof r);
    tt = -1;
    q[++tt] = 0;
    f[0] = -1;
    for(int i = 1; i <= m; i++)
    {
        while(f[q[tt]] >= f[i]) tt--;
        l[i] = i - q[tt];
        q[++tt] = i;
    }
    
    tt = -1;
    q[++tt] = m + 1;
    f[m + 1] = -1;
    for(int i = m; i ; i--)
    {
        while(f[q[tt]] >= f[i]) tt--;
        r[i] = q[tt] - i;
        q[++tt] = i;
    }
    
    int res = 0;
    for(int i = 1; i <= n; i++)
    {
        res = max(res, (l[i] + r[i] - 1) * f[i]);
    }
    
    return res;
}

int main()
{
    cin >> n >> m;
    for(int i = 1; i <= n; i++) 
    {
        for(int j = 1; j <= m; j++)
        {
            cin >> g[i][j];
            if(g[i][j] == 'F')
            {
                f[i][j] = f[i - 1][j] + 1;
            }
            else
            {
                f[i][j] = 0;
            }
        }
    }
    
    int res = 0;
    
    for(int i = 1; i <= n; i++)
    {
        res = max(res, work(f[i]));
    }
    
    cout << res * 3 << endl;
    
    return 0;
    
}


  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值