单调栈应用(poj2559最大矩形、最大01子矩阵)

首先介绍一下单调栈(或单调数组)。

单调栈是一个这样的数据结构:
1. 这个数组一定是单调递增(或单调递减)的数组;
2. 可以在末尾添加元素;
3. 在添加元素的过程中,允许弹出尽可能少的元素是数组依旧保持单调性;

例如:原来的单调数组是(1,3,5,7,9),现在希望添加一个元素6。此时我们把末尾的9,7两个元素均删除,插入6在末尾,新的单调数组变成(1,3,5,6)。

可以看出,当我们需要在这个数组末尾添加元素时,需要删除末尾所有大于新元素的元素,再进行添加。

用std::stack实现单调栈的过程很简单,在这里不多赘述。
但是其时间复杂度的常数很大,效率并没有手写的单调数组高(是手写的单调数组的近两倍)。
下面给出手写单调队列的伪代码:

void run(){
   
	for(int i=1;i<=n;i++){
   
		for(;nstack>0&&h[stack[nstack]]>h[i];nstack--){
   
			弹出栈顶的元素,并用该元素处理更新答案 
			//stack[]数组中储存的是下标 
		}
		stack[++nstack]=i;//存入i这个元素 
	}
}

下面给出一个最常规的单调数组的例题:

(POJ2559)如下图所示,在一条水平线上方有n(n<=100000)个矩形,求包含于这些矩形的并集内部的最大矩形的面积。
poj2559
不难想到,对于包含第i根柱子,高度为h[i]的矩形,其宽度最大是向左右两边延伸,直到遇到第一根比h[i]矮的柱子。

如果对于每一个i,暴力枚举其左右两边的柱子,其复杂度为O(n²)。

思考我们的算法需要对于每个i,都找到h[i]前后恰好第一个比它低的柱子,从而求出一个候选长方形的面积,用这个面积去更新答案。

为什么单调数组可以起到这个作用?

在这里插入图片描述
来看这个例子,第2、5个元素因为比后面的元素大,从而从单调数组中被弹出,单调数组中储存的下标为(1,3,4,5,7)。

在这个单调数组中,每一个元素的前一个元素都是“左边第一个比它矮的柱子”,因为比它高的都被它弹掉了,那么它向左最大能延伸的宽度就是和它前一个元素的下标之差。

那么如何去寻找右边后一个比它矮的元素呢?

  • 数组在插入时强制删除比新元素大的元素
  • 对于删除的元素,由于数组单调递增的原因,新元素就是右边第一个比它矮的。
  • 对于每个被弹出的元素,算出其对应的候选矩形,用来更新答案即可。

运用单调栈这种数据结构,使得复杂度降低到了O(n);

代码:

#include <cstdio>
#include <algorithm>
inline int read() {
   
    int x = 0;
    char ch = getchar();
    while ('0' <= ch && ch <= '9') {
   
        x = x * 10 + ch - '0';
        ch = getchar();
    }
    return x;
}
const int maxn = 1000011;
int n, h[maxn], stack[maxn];
int top, ans;
int main() {
   
    n = read();
    h[n + 1] = 0;
    for (int i = 1; i <= n; i++) h[i] = read();
    for (int i = 1
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值