浅谈最大化子矩阵问题
文章目录
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 最大子矩阵问题
| 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;
}

浙公网安备 33010602011771号