题目描述 Description
在一个0,1方阵中找出其中最大的全0子矩阵,所谓最大是指O的个数最多。
输入描述 Input Description
输入文件第一行为整数N,其中1<=N<=2000,为方阵的大小,紧接着N行每行均有N个0或1,相邻两数间严格用一个空格隔开。
输出描述 Output Description
输出文件仅一行包含一个整数表示要求的最大的全零子矩阵中零的个数。
样例输入 Sample Input
5
0 1 0 1 0
0 0 0 0 0
0 0 0 0 1
1 0 0 0 0
0 1 0 0 0
样例输出 Sample Output
9
mdzz看到这题我是一脸懵逼的。。想了半天还是没头绪看了个看题解发现是悬线法。。。于是跑去学了悬线法。。。
悬线法
时间复杂度O(NM) 空间复杂度O(NM)
定义
有效竖线:除了两个端点外,不覆盖任何一个障碍点的竖直线段。
悬线:上端覆盖了一个障碍点或者到达整个矩形上边界的有效线段。
每个悬线都与它底部的点一一对应,矩形中的每一个点(矩形顶部的点除外)都对应了一个悬线。
悬线的个数=(N-1)*M;
如果把一个极大子矩形按照横坐标的不同切割成多个与y轴平行的线段,那么其中至少有一个悬线。
如果把一个悬线向左右两个方向尽可能的移动,那么就得到了一个矩形,我们称它为悬线对应的矩形。
悬线对应的矩形不一定是极大子矩形,因为下边界可能还可以向下扩展。
设计算法:
原理:所有悬线对应矩形的集合一定包含了极大子矩形的集合。
通过枚举所有的悬线,找出所有的极大子矩形。
算法规模:
悬线个数=(N-1)×M
极大子矩形个数≤悬线个数
具体方法:
设 H[i,j]为点(i,j)对应的悬线的长度。
L[i,j]为点(i,j)对应的悬线向左最多能够移动到的位置。
R[i,j]为点(i,j)对应的悬线向右最多能够移动到的位置。
!考虑点(i,j)对应的悬线与点(i-1,j)对应的悬线的关系(递推思想):
如果(i-1,j)为障碍点,那么,(i,j)对应的悬线长度1,左右能移动到的位置是整个矩形的左右边界。
即 H[i,j]=1,
L[i,j]=0,R[i,j]=m
如果(i-1,j)不是障碍点,那么,(i,j)对应的悬线长度为(i-1,j)对应的悬线长度+1。
即 H[i,j]=H[i-1,j]+1
•如果(i-1,j)不是障碍点,那么(i,j)对应的悬线左右能移动的位置要在(i-1,j)的基础上变化。
L[i-1,j]
L[i,j]=max
(i-1,j)左边第一个障碍点的位置
•同理,也可以得到R[i,j]的递推式
R[i-1,j]
R[i,j]=min
(i-1,j)右边第一个障碍点的位置
AC代码
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
using namespace std;
const int maxn = 2333;
int h[maxn][maxn],l[maxn][maxn],r[maxn][maxn];
int n,maxl,maxr,ans,martrix[maxn][maxn];
int main()
{
int i,j;
scanf("%d",&n);
for(i=1;i<=n;++i)
for(j=1;j<=n;++j)
scanf("%d",&martrix[i][j]);
for(i=1;i<=n;++i) r[0][i]=n;
for(i=1;i<=n;++i)
{
maxr=n; maxl=1;
for(j=1;j<=n;++j)
if(martrix[i][j])
{
maxl=j+1;
h[i][j]=l[i][j]=0;
}
else
{
h[i][j]=h[i-1][j]+1;
l[i][j]=max(maxl,l[i-1][j]);
}
for(j=n;j>0;--j)
if(martrix[i][j])
{
maxr=j-1;
r[i][j]=n;
}
else
{
r[i][j]=min(maxr,r[i-1][j]);
ans=max(ans,(r[i][j]-l[i][j]+1)*h[i][j]);
}
}
printf("%d\n",ans);
return 0;
}