浅谈最大化子矩阵问题

文章目录
1. 矩阵
2. 最大化子矩阵问题
3. 解法
-- 3.1. 初级解法

1 矩阵

严格地说,这里的矩阵并不是严格要求的;确切地说只是二位数组或者矩形排列的一些物体(items),但是为了方便表达,就使用矩阵这一名词了.

我们定义一个矩阵$\text{Matrix}[r,c]$是一个r行c列的二维阵列(不一定是数组),其中将$\text{Matrix}[r_1]$称为一行,或$\text{Matrix}[r_1,1..c](1\le r_1\le r)$,将$\text{Matrix}[1..r,c_1](1\le c_1\le c)$称为一列.
我们定义一个矩阵$\text{Matrix}[r,c]$中的连续一块$\text{Sub_Matrix}[r_1..r_2,c_1..c_2](1\le r_1\le r_2\le r,1\le c_1\le c_2\le c)$为$\text{Matrix}[r,c]$的一个子矩阵$\text{Sub}\left( \text{Matrix},r_1,r_2,c_1,c_2\right)$
      / 1 0 1 1 0 1 \
      | 0 1 1 0 0 0 |
M_1 = | 0 1 0 1 0 0 |
      | 1 1 0 0 1 0 |
      \ 1 1 0 0 1 1 /
M_2 = / 0 1 0 \ = Sub(M_1,3,4,3,5)
      \ 0 0 1 /
M_2 是 M_1 的子矩阵
(其中灰色背景+下划线部分).

 2 最大子矩阵问题

设$M_1$=
1 0 1 1 0 1
0 0 0 1 0 0
0 0 1 1 0 0
1 0 0 0 0 0
0 1 0 0 0 0
1 1 1 0 0 0

其中,我们将1称为障碍点,我们要找到$M_1$最大(面积最大)的一个子矩阵$M_2$,使$M_2$中不含任何障碍点.这个问题,叫做最大子矩阵问题.

这个例子中,我们简单地看到,用蓝色标出的这一部分就是$M_1$的最大子矩阵.

3 解法

3.1 解法1 最直接的解法

我们可以暴力枚举.

先枚举左上角的点,再枚举右下角的点,最后在生成的子矩阵中按行枚举每一个点.这种算法的复杂度大约是$\text{O}\left( r^3 \cdot c^3\right)$.

3.2 解法2 稍微好一些的解法

首先预处理,用前缀和表示每行的1数,相减若是0那么在一个范围内没有1.$\text{O}\left( r^3 \cdot c^2)$.(可以将$c,r$互换随你变= =)

Matrix MaxSubMatrix(Matrix M){
	int i,j,k,l,max=0
	Matrix prefixSum,MaxMatrix,tmpMatrix
	foreach(M,byrow,(int i,int x,int y)=>void{
		prefixSum[x][y]=prefix[x][y-1]+i
	})
	foreach(M,byrow,(int i,int x,int y)=>void{
		foreach(rangeMatrix(M,x,y,>r,>c),byrow,(int i,int xx,int yy)=>void{
			if(foreach(rangeMatrix(M,x,y,xx,yy),rows,(int row)=>void{
				return OP::MINUS(prefix[row][yy,y-1])
			}),false,OP::BOOLOR){
				tmpMatrix=subMatrix(M,x,y,xx,yy)
				if(area(tmpMatrix)>max){
					max=area(tmpMatrix)
					MaxMatrix=tmpMatrix
				}
			}
		})//伪代码风格凶残
	})
	return MaxMatrix
}

3.3 解法3 更好一些的算法

在找到上面那个算法的时候,我们可以想到一个更好一些的算法,比如我们有这么一个矩阵

遍历到如图的红点

第一次扫描一行没遇到障碍点

记录这个矩形,扫描下一行.注意由于这个点定了,下一行从这行的最后一个扫描到的同样位置那个数,找到第一个前缀和差为0的格子

那么下一行应该从蓝色各种数起

下一行这里由于有一个1

应该再向前一格数

一直扫到底

那么这个就是这个点上的最大子矩阵

时间复杂度$\text{O}\left( r\cdot c\cdot \left(r+c\right) \right)$.为什么呢?

对于某个点,这个扫描列最多走一遍行一遍列,也就是$r+c$

三次方级别.

换个思想,我们还可以发现另一种解法.

选中两列

一行一行扫描,用前缀和判断是不是全0

仔细想一想,很容易在$\text{O}\left( r\right)$时间内找出它这里最长的一些没有障碍的连续的行.DP即可

时间复杂度$\text{O}\left( c^2\cdot r\right)$

3.4 最佳的办法

其实这道题是可以在$\text{O}\left( c\cdot r\right)$时间内解决的.

选定一行,让我们想一想如何求出以这行为底边的最大无障碍矩形.

标绿色的部分是总可以选择的方块.我们要用它们和低下非1的方块构造最大的矩形

每个柱状图的高度统计在下面(纯手数)

显而易见,朴素的办法是$\text{O}\left( c^3\right)$的.加上RMQ可以达到$\text{O}\left( c^2\right)$

但是我们要做得更好.我们要把它优化到$\text{O}\left( c\right)$

我们可以逐个扫描.设扫到第$i$个高度$h[i]$,设一个栈$stack[]$

foreach i in row:
if h[i] >= stack.top.second:
push (i,h[i]) stack[]
else:
while stack.top.second>=h[i]:
t = pop stack
area = ( i - ( stack.empty ? t.first : stack.top.first + 1 ) ) * t.second
_max=max(_max,area)
push (i,h[i]) stack[]
return _max

最后发一下我的代码,USACO 6.1.2

#include <cstdio>
int R,C,P,i,j,k,l,ma;
bool board[4000][4000];
int height[4000];
struct stackItem{
	int height,y;
} stack[4000];
int slen,tmp;
int maxArea(){
	stack[slen].height=height[0];
	stack[slen].y=0;
	k=0;
	for(j=1;j<=4000;++j){
		if(height[j]>=stack[slen].height){
			++slen;
			stack[slen].height=height[j];
			stack[slen].y=j;
		}else{
			while(stack[slen].height>=height[j]&&slen>=0){
				tmp=(j-(slen?stack[slen-1].y-1:stack[slen].y))*stack[slen].height;
				--slen;
				k=k>tmp?k:tmp;
			}
		}
	}
	return k;
}
int main(){
	scanf("%d%d%d",&R,&C,&P);
	for(i=0;i<P;++i){
		scanf("%d%d",&j,&k);
		board[j-1][k-1]=true;
	}
	for(i=0;i<R;++i){
		for(j=0;j<C;++j){
			height[j]=board[i][j]?0:height[j]+1;
		}
		slen=0;
		if(maxArea()>ma) ma=k;
	}
	printf("%d",ma);
	return 0;
}

 

posted @ 2015-02-18 19:45  zball  阅读(436)  评论(0编辑  收藏  举报