Codevs 1159 最大全0子矩阵
题目描述 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
https://wenku.baidu.com/view/cd82b3f5e87101f69f3195bc.html
/* 感觉很像“土豪聪要请客”,就用前缀和用同样的方法做了,结果超时 */ #include<iostream> #include<cstdio> using namespace std; int n,f[2010][2010],ans; int main(){ //freopen("Cola.txt","r",stdin); scanf("%d",&n); int x; for(int i=1;i<=n;i++) for(int j=1;j<=n;j++){ scanf("%d",&x); f[i][j]=x^1; f[i][j]+=f[i][j-1]; } for(int i=1;i<=n;i++){ for(int j=i;j<=n;j++){ int sum=0,mx=0; for(int k=1;k<=n;k++){ if(f[k][j]-f[k][i-1]==(j-i+1))sum++; else sum=0; mx=max(mx,sum); } ans=max(ans,mx*(j-i+1)); } } printf("%d",ans); }
学习材料:王知昆《浅谈用极大化思想解决最大子矩阵问题》 【最大子矩阵问题】 在一个给定的矩形中有一些障碍点,找出内部不包含障碍点的、轮廓与整个矩形平行或重合的最大子矩形。 【定义子矩形】 有效子矩形:内部不包含障碍点的、轮廓与整个矩形平行或重合的子矩形。 极大子矩形:每条边都不能向外扩展的有效子矩形。 最大子矩形:所有有效子矩形中最大的一个(或多个)。 【极大化思想】 在一个有障碍点的矩形中最大子矩形一定是极大子矩形。 设计算法的思路:枚举所有的极大子矩形,找到最大子矩形。 设NM分别为整个矩形的长和宽,S为内部的障碍点数。 【算法1】 时间复杂度:O(S^2) 空间复杂度:O(S) 由于极大子矩形的每一条边都不能向外扩展,那么极大子矩阵的每条边要么覆盖了障碍点,要么与整个矩形的边界重合 基本算法:枚举上下左右四个边界,然后判断组成的矩形是否是有效子矩形。 复杂度:O(S^5) 可以改进的地方:产生了大量的无效子矩形。 初步改进的算法:枚举左右边界,然后对处在边界内的点排序,每两个相邻的点和左右边界组成一个矩形。 复杂度:O(S^3) 可以改进的地方:枚举了部分不是极大子矩形的情况。 综上,设计算法的方向: 1、保证每一个枚举的矩形都是有效的。 2、保证每一个枚举的矩形都是极大的。 算法的过程: 枚举极大子矩形的左边界——>根据确定的左边界,找出相关的极大子矩形——>检查和处理遗漏的情况 (1)按照横坐标从小到大的顺序将所有的点编号为1,2,3... (2)首先选取1号点作为要枚举的极大子矩形的左边界,设定上下边界为矩形的上下边界 (3)从左到右扫描,第一次到2号点,确定一个极大子矩形,修改上下边界;第二次找到3号点,以此类推。 (4)将左边界移动到2号点,3号点,,,以同样的方法枚举 遗漏的情况: 1、矩形的左边界与整个矩形的左边界重合。解决方法:用类似的方法从左到右扫一遍 2、矩形的左边界与整个矩形的左边界重合,且矩形的右边界与整个矩形的右边界重合。解决方法:预处理时增加特殊判断。 优点:利用的极大化思想,复杂度可以接受,编程实现简单。 缺点:使用有一定的局限性,不适合障碍点较密集的情况。 【算法2】 时间复杂度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)右边第一个障碍点的位置 优点: 复杂度与障碍点个数没有直接关系。 缺点:障碍点少时处理较复杂,不如算法1。 代码实现具体见WC2002 奶牛浴场(算法1) codevs1159最大全0子矩阵(算法2)
#include<iostream> #include<cstdio> using namespace std; #define maxn 2010 int n,h[maxn],l[maxn],r[maxn]; int ans=0,map[maxn][maxn]; int main(){ //freopen("Cola.txt","r",stdin); scanf("%d",&n); for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) scanf("%d",&map[i][j]); for(int i=1;i<=n;i++)h[i]=0,l[i]=1,r[i]=n; int ll,rr; for(int i=1;i<=n;i++){ ll=0,rr=n+1; for(int j=1;j<=n;j++){ if(map[i][j])h[j]=0,l[j]=1,ll=j;//遇到障碍 else h[j]++,l[j]=max(l[j],ll+1); } for(int j=n;j>=1;j--){ if(map[i][j])r[j]=n,rr=j;//遇到障碍 else r[j]=min(r[j],rr-1),ans=max(ans,h[j]*(r[j]-l[j]+1)); } } printf("%d",ans); return 0; }