动态规划 | 最大长方形 | Largest Rectangle | C/C++实现

问题描述

现有 H × W H×W H×W个边长为1cm的正方形瓷砖排列在一起,其中有一部分瓷砖沾有污迹,求仅由干净瓷砖构成的最大长方形的面积。

输入:
H H H W W W
c 1 , 1 c_{1,1} c1,1 c 1 , 2 c_{1,2} c1,2 c 1 , W c_{1,W} c1,W
c 2 , 1 c_{2,1} c2,1 c 2 , 2 c_{2,2} c2,2 c 2 , W c_{2,W} c2,W

c H , 1 c_{H,1} cH,1 c H , 2 c_{H,2} cH,2 c H , W c_{H,W} cH,W
第1行输入2个整数H、W,用空格隔开。接下来H行输入H×W个代表瓷砖的整数 c i j c_{ij} cij c i j c_{ij} cij为1表示瓷砖沾有污渍,为0表示干净
输出:
输出面积的最大值,占1行
限制:
1 ≤ H,W ≤ 1400

输入示例

4 5
0 0 1 0 0
1 0 0 0 0
0 0 0 1 0
0 0 0 1 0

输入示例

6

讲解

我们求解正方形时(参见:动态规划 | 最大正方形 | Largest Square | C/C++实现)所用的算法在这道题中并不适用,需另寻他法。

首先,建立一个二维数组T[MAX][MAX],在表T中记录各元素向上存在多少个连续的干净瓷砖。对各列使用动态规划法可以很轻松的求出T

我们把表T的每行都看成一个直方图,本题就成了求直方图内最大长方形的问题。于是我们转为考虑求直方图中最大长方形的面积。这里最容易想到的是穷举法,我们可以列出直方图的所有端点,求出各个范围内的最大长方形的面积(以该范围内最小值为高的长方形的面积),然后取其中最大值。但是此法整体的复杂度高达 O ( H W 2 ) O(HW^2) O(HW2) O ( H 2 W ) O(H^2W) O(H2W),不够理想

其实在解决这个问题时,只要用栈替代数组记录局部问题的解,就能大幅提高求最优解的效率。栈中记录”仍有可能扩张的长方形的信息(记为rect)“。rect内含有两个信息,一个是长方形的高height,另一个是其左端的位置pos。首先我们将栈置空,接下来对于直方图的各个值 H i ( i = 0 , 1 , . . . , W − 1 ) H_i(i=0,1,...,W-1) Hi(i=0,1,...,W1),创建以 H i H_i Hi为高,以其下标 i 为左端位置的长方形rect,然后进行以下处理:
1.如果栈为空:
将rect压入栈。
2.如果栈顶长方形的高小于rect的高:
将rect压入栈。
3.如果栈顶长方形的高等于rect的高:
不作处理。
4.如果栈顶长方形的高大于rect的高:
只要栈不为空,且栈顶长方形的高大于等于rect的高,就从栈中取出长方形,同时计算其面积并更新最大值。长方形的长等于”当前位置 i”与之前记录的“左端位置pos”的差值
将rect压入栈。另外,这个rect的左端位置pos为最后从栈中取出的长方形的pos值。

处理各直方图时,向栈添加或删除长方形的操作需要消耗 O ( W ) O(W) O(W)。搜索长方形的问题要对每一行执行一次这种处理,复杂度为 O ( H W ) O(HW) O(HW)

AC代码如下

#include<stdio.h>
#include<iostream>
#include<stack>
#include<algorithm>
#define MAX 1400

using namespace std;

struct Rectangle { int height; int pos; };

int getLargestRectangle(int size, int buffer[]) {
	stack<Rectangle> S;
	int maxv = 0;
	buffer[size] = 0;
	
	for(int i = 0; i <= size; i++) {
		Rectangle rect;
		rect.height = buffer[i];
		rect.pos = i;
		if(S.empty() ) {
			S.push(rect);
		} else {
			if(S.top().height < rect.height) {
				S.push(rect);
			} else if(S.top().height > rect.height) {
				int target = i;
				while(!S.empty() && S.top().height >= rect.height) {
					Rectangle pre = S.top(); S.pop();
					int area = pre.height * (i-pre.pos);
					maxv = max(maxv, area);
					target = pre.pos;
				}
				rect.pos = target;
				S.push(rect);
			}
		}
	}
	return maxv;
}

int H, W;
int buffer[MAX][MAX];
int T[MAX][MAX];

int getLargestRectangle() {
	for(int j = 0; j < W; j++) {
		for(int i = 0; i < H; i++) {
			if(buffer[i][j]) {
				T[i][j] = 0;
			} else {
				T[i][j] = (i > 0) ? T[i-1][j] + 1 : 1;
			}
		}
	}
	
	int maxv = 0;
	for(int i = 0; i < H; i++) {
		maxv = max(maxv, getLargestRectangle(W, T[i]));
	}
	
	return maxv;
}

int main(){
	scanf("%d %d", &H, &W);
	for(int i = 0; i < H; i++) {
		for(int j = 0; j < W; j++) {
			scanf("%d", &buffer[i][j]);
		}
	}
	cout<<getLargestRectangle()<<endl;
	
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值