单调栈基础

目录

一.引入

二.例题

1.题目:最大矩形面积

题目描述

输入

输出

输入样例

输出样例 

 2.题解

思路

 代码

三.例题升级版

1.题目:矩形牛棚

题目描述

输入

输出

样例输入

样例输出 

 2.思路

四.总结


一.引入

单调栈是个什么东西?就是只单调上升、下降、不上升、不下降的序列。既可以用数组模拟,也可以就用STL的栈。时间复杂度是O(n),代码如下:


1.数组模拟

for (register int i = 1; i <= n; i ++){ //a表示数组模拟的栈,b是原数组,n是数组的长度,这是单调上升序列
    if (b[i] < b[a[tail]]){
        for ( ; tail > 0; tail --){
            if (b[a[tail]] < b[i])
                break;
        }
    }
    a[++ tail] = i;
}

2.STL(要注意判断是否是空栈)

for (register int i = 1; i <= n; i ++){ //b是原数组,a是栈,单调上升序列
        int tmp = 0; 
        if (!a.empty()) //注意判断是否是空栈,如果是空栈,还要a.top(),就会编译错误
            tmp = a.top(); 
        if (b[i] < b[tmp]){ 
            while (!a.empty()){ 
                tmp = a.top(); 
                if (b[tmp] < b[i]) 
                    break; 
                a.pop(); 
            } 
        } 
        a.push(i); 
    } 

光有模板有什么用?题目才现实:

二.例题

1.题目:最大矩形面积

题目描述

在X轴上水平放置着 N 个条形图,这 N 个条形图就组成了一个柱状图,每个条形图都是一个矩形,每个

矩形都有相同的宽度,均为1单位长度,但是它们的高度并不相同。

例如下图,图1包含的矩形的高分别为2,1,4,5,1,3,3 单位长度,矩形的宽为1单位长度。

你的任务就是计算柱状图中以X轴为底边的最大矩形的面积。图2阴影部分就是上述例子的最大矩形面积。

输入

输入数据的第一行是一个整数 N(1≤ N ≤100000),表示柱状图包含 N 个矩形。

紧接着 N 个整数h1,...,hn(0≤ hi ≤20000, 1≤ i≤ N),表示柱状图中按从左到右顺序给出的矩形

的高度。矩形的宽度为1。

输出

输出一个整数S,表示以X轴为底边的最大矩形的面积。

输入样例

7

2 1 4 5 1 3 3 

输出样例 

8

 


2.题解


思路

这道题我们很容易想到,若要广告牌面积最大,广告牌的长不能确定,但是它的高一定等于其中某建筑的高。一定是这样,如下图:(阴影面积代表广告牌的面积)

          图1:                              图2:

很明显,图1的广告牌面积大于图1的广告牌面积,而且图1广告牌的高等于h[1]。

这时很容易想到一个爆搜思路:枚举每一个建筑,找到它前面第一个比它矮的建筑,和后面第一个比它矮的建筑,这两栋建筑中间有多少栋建筑,广告牌就有多长。为何如此呢?因为广告牌的每一个位置都不能悬空,如果前面的建筑比它矮,那么广告牌就不能再伸长了。 

既如此,我们何不直接用单调栈呢?用一个单调上升的栈,再栈中的一个元素i的前一个元素j代表前面第一个比i建筑矮的建筑,而后面第一个比i矮的建筑就直接在每一次新元素入栈的时候判断就行了。


以样例为例:原数组h,单调栈a

1.

2.

 

3.

4.

5.

6.

7.

8.清空所有


相信大家一定自己已经推理出ans的计算方法了:ans = max {ans, h[a[tail]] * (i - a[tail - 1] - 1)}//前面第一个比它小的和后面第一个比它小的坐标之差,再减1。

 


代码

#include <cstdio> 
#include <iostream> 
#include <cstring> 
using namespace std; 
#define M 100005 
int n, h[M], tail, a[M], ans; 
int main (){ 
    scanf ("%d", &n); 
    for (int i = 1; i <= n; i ++) 
        scanf ("%d", &h[i]); 
    for (register int i = 1; i <= n + 1; i ++){ //n+1是为了把栈中所有元素都踢出去
        if (h[i] < h[a[tail]]){ 
            for ( ; tail > 0; tail --){ 
                if (h[a[tail]] <= h[i]) 
                    break; 
                ans = max (ans, h[a[tail]] * (i - a[tail - 1] - 1)); 
            } 
        } 
        a[++ tail] = i; 
    } 
    printf ("%d", ans); 
    return 0; 
} 

当然也可以用STL实践,大家下去试,我在这里就不说了。

再提一个小问题:如果此题改成无限输入呢?也许大家会认为直接把代码改成while(~scanf()),再tail清零就行了。这样你会错的,万万使不得。必须把h数组清零。因为我们次循环到n+1,万一上一个数据的n大于了这个数据的n,h[n + 1]就会有值,那么你就有可能错!


三.例题升级版


1.题目:矩形牛棚

题目描述

到底是个资本家,Farmer John想通过买更多的奶牛来扩大它的生意。它需要给奶牛建造一个新的牛棚。 FJ买了一个矩形的R(1 <= R <= 3000)行C(1 <= C <= 3000)列的牧场。不幸的是,他发现某些1 x 1的区域被损坏了,所以它不可能在把整个牧场建造成牛棚了。

FJ数了一下,发现有P(1 <= p <= 30000)个1 x 1的损坏区域并且请你帮助他找到不包含损坏区域的面积最大的牛棚。 * (对同一个损坏区域可能有多次描述——aLeN.PN注)

输入

第1行: 三个空格隔开的整数 R, C, and P.

第2..P+1行: 每行包含两个空格隔开的整数, r和c, 给出一个损坏区域的行号和列号.

输出

第1行: 牛棚的最大可能面积

样例输入

3 4 2

1 3

2 1 

样例输出 

 


2.思路

这道题可有意思了,换成了一个二维的图,题目的图是这样:

实际上一张图就是很多个单调栈组合起来的,可以看成每行一个单调栈,也可以看成每列一个单调栈,这里我把它看成每行一个单调栈。现在的难点就是找到每一个格子所代表的建筑有多高(从上往下数)。如:(1,1)高1,(1,2)高3,(1,3)高0。如果在维护单调队列的时候边维护边找,就是一个三重循环,一定超时(O(c*r^2)),何不在预处理的时候就解决这个问题呢?

预处理方法:

首先要标记坏的格子,然后外层循环枚举列数,在内层第一个循环用一个数组prepare从小到大地存下这一列那几行的格子数坏的,第二个循环从小到大枚举这一列的每一行,再用一个下标k控制prepare数组,如果这一行小于或等于prepare[k],这个格子的高度就是prepare[k]-j,如果这一行大于prepare[k],就让k++,再prepare[k]-j就行了。


代码:

for(register int i = 1; i <= c; i ++){ 
        int t = 0; 
        for(register int j = 1; j <= r; j ++){ 
            if(flag[j][i]) 
                prepare[++ t] = j; 
        } 
        prepare[++ t] = r + 1; 
        t = 1; 
        for(register int j = 1; j <= r; j ++){ 
            if(j <= prepare[t]){ 
                high[j][i] = prepare[t] - j; 
            } 
            else{ 
                t ++; 
                high[j][i] = prepare[t] - j; 
            } 
        } 
    } 

之后的二重循环单调队列就不用说了撒。


整体代码如下所示:

#include <cstdio> 
#define max(a, b) a > b ? a : b 
#define M 3005 
using namespace std; 
int r, c, p, ans, a[M], tail, high[M][M], prepare[M]; 
bool flag[M][M]; 
int main (){ 
    scanf("%d %d %d", &r, &c, &p); 
    int x, y; 
    for(register int i = 1; i <= p; i ++){ 
        scanf("%d %d", &x, &y); 
        flag[x][y] = 1; 
    } 
    for(register int i = 1; i <= c; i ++){ 
        int t = 0; 
        for(register int j = 1; j <= r; j ++){ 
            if(flag[j][i]) 
                prepare[++ t] = j; 
        } 
        prepare[++ t] = r + 1; 
        t = 1; 
        for(register int j = 1; j <= r; j ++){ 
            if(j <= prepare[t]){ 
                high[j][i] = prepare[t] - j; 
            } 
            else{ 
                t ++; 
                high[j][i] = prepare[t] - j; 
            } 
        } 
    } 
    for(register int i = 1; i <= r; i ++){ 
        tail = 0; 
        for(register int j = 1; j <= c + 1; j ++){ 
            if(high[i][j] < high[i][a[tail]]){ 
                for( ; tail > 0; tail --){ 
                    if(high[i][a[tail]] <= high[i][j]) 
                        break; 
                    ans = max (ans, high[i][a[tail]] * (j - a[tail - 1] - 1)); 
                } 
            } 
            a[++ tail] = j; 
        } 
    } 
    printf("%d\n", ans); 
} 
  

四.总结

单调栈虽然好用,但是有很多坑的地方,并且有时还要用其他的处理来优化单调队列,所以要多练,多用,活学活用!谢谢!

猜你喜欢

转载自blog.csdn.net/weixin_43908980/article/details/85028509