文章目录
1. 矩阵
2. 最大化子矩阵问题
3. 解法
-- 3.1. 初级解法
1 矩阵
严格地说,这里的矩阵并不是严格要求的;确切地说只是二位数组或者矩形排列的一些物体(items),但是为了方便表达,就使用矩阵
这一名词了.
我们定义一个矩阵$ ext{Matrix}[r,c]$是一个r行c列的二维阵列(不一定是数组),其中将$ ext{Matrix}[r_1]$称为一行,或$ ext{Matrix}[r_1,1..c](1le r_1le r)$,将$ ext{Matrix}[1..r,c_1](1le c_1le c)$称为一列.
我们定义一个矩阵$ ext{Matrix}[r,c]$中的连续一块$ ext{Sub_Matrix}[r_1..r_2,c_1..c_2](1le r_1le r_2le r,1le c_1le c_2le c)$为$ ext{Matrix}[r,c]$的一个子矩阵$ ext{Sub}left( ext{Matrix},r_1,r_2,c_1,c_2
ight)$
/ 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 最直接的解法
我们可以暴力枚举.
先枚举左上角的点,再枚举右下角的点,最后在生成的子矩阵中按行枚举每一个点.这种算法的复杂度大约是$ ext{O}left( r^3 cdot c^3 ight)$.
3.2 解法2 稍微好一些的解法
首先预处理,用前缀和表示每行的1数,相减若是0那么在一个范围内没有1.$ ext{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
应该再向前一格数
一直扫到底
那么这个就是这个点上的最大子矩阵
时间复杂度$ ext{O}left( rcdot ccdot left(r+c ight) ight)$.为什么呢?
对于某个点,这个扫描列最多走一遍行一遍列,也就是$r+c$
三次方级别.
换个思想,我们还可以发现另一种解法.
选中两列
一行一行扫描,用前缀和判断是不是全0
仔细想一想,很容易在$ ext{O}left( r ight)$时间内找出它这里最长的一些没有障碍的连续的行.DP即可
时间复杂度$ ext{O}left( c^2cdot r ight)$
3.4 最佳的办法
其实这道题是可以在$ ext{O}left( ccdot r ight)$时间内解决的.
选定一行,让我们想一想如何求出以这行为底边的最大无障碍矩形.
标绿色的部分是总可以选择的方块.我们要用它们和低下非1的方块构造最大的矩形
每个柱状图的高度统计在下面(纯手数)
显而易见,朴素的办法是$ ext{O}left( c^3 ight)$的.加上RMQ可以达到$ ext{O}left( c^2 ight)$
但是我们要做得更好.我们要把它优化到$ ext{O}left( c ight)$
我们可以逐个扫描.设扫到第$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; }