http://codevs.cn/problem/1428/
题意:给定一个N行M列的01矩阵,要求找出元素数最多的子正方阵和子矩阵,使得其中任意两个相邻元素均不同,N,M<=2000。
任意相邻两个元素均不同就意味着,要么奇数位上全0偶数位上全1,要么奇数位上全1偶数位上全0,这里的奇偶指的是行数和列数之和的奇偶性。
这个问题和经典的最大全0子矩阵有着很大的相似性,我们可以考虑转化过去。先来讲一讲最大全0子矩阵的做法。
建立二维数组h[],h[i][j]表示从左向右到元素[i][j]连续的0的数量。如下面这个01矩阵:
0 0 0
0 0 0
0 0 1
h[]数组的值应该为:
1 2 3
1 2 3
1 2 0
这个数组的值可以用简单的递推O(n^2)内求出。
接下来,对于每个h[i][j],求出其正上方和正下方各到何处仍有连续的h[][j]不小于h[i][j],如上面的例子,对于h[2][2]=2,向上可以拓展到h[1][2],向下可以拓展到h[3][2]。
设向上连续到h[l][j],向下连续到h[r][j],则得到了一个(r-l+1)行h[i][j]列的矩阵,用乘积对当前最大值进行修改即可。
但是,如果要直接扫描求l和r,在最坏情况下,当h[][j]单调上升时,从每个[][j]开始向下都会扫描到最下端,这样单次扫描O(n),总时间复杂度O(n^3),还是无法满足要求。
事实上,我们要寻找的就是对于每个元素,正上方和正下方第一次出现h[]值小于当前位置h[]值得位置,将该位置加1或减1即得到l和r。
这里我们要用到一种叫做单调栈的结构,保持栈底到栈顶的全部元素都是单调的。
每当有新元素要入栈时,若直接入栈不能保证单调性,则不断将栈顶元素弹出直到可以入栈再入栈。
如序列1,0,3,2,5,4,要找出从4向左第一个比4小的元素,则先将前5个数入栈得到0,2,5,要将4入栈,需先将5弹出,则弹出后的栈顶元素2即为要寻找的元素。
由于每个元素最多进出栈一次,而找元素和维护同时进行的,因此对于每一列的各元素,寻找l和r是线性O(n)的,因而整个算法是O(n^2)的。
接下来将上述算法应用到题目中,还是要考虑到满足条件的01子矩阵矩阵的两种情况:
对于前者,可以先将所有偶数位上的数取反然后做一次最大全0矩阵,对于后者则是对奇数位操作,这样只要在修改后各做一次即可。
关于子正方阵,只要将每次得到的长和宽中取较小值平方即可。
代码:
#include<cstdio> #define rpt(i,l,r) for(i=l;i<=r;i++) #define rpd(i,r,l) for(i=r;i>=l;i--) int a[2015][2015],b[2015][2015],l[2015][2015],r[2015][2015]; int n,m,i,j,ans1,ans2; int st[2015],tp; int max(int x,int y){ if(x>y) return x; else return y; } int min(int x,int y){ if(x<y) return x; else return y; } void work(){ rpt(i,1,n) rpt(j,1,m){ if(a[i][j]==0) b[i][j]=0; else if(j==1) b[i][j]=1; else b[i][j]=b[i][j-1]+1; } rpt(j,1,m){ st[tp=0]=0; rpt(i,1,n){ while(b[i][j]<=b[st[tp]][j]) tp--; l[i][j]=st[tp]+1; st[++tp]=i; } st[tp=0]=n+1; rpd(i,n,1){ while(b[i][j]<=b[st[tp]][j]) tp--; r[i][j]=st[tp]-1; st[++tp]=i; } } rpt(i,1,n) rpt(j,1,m){ ans1=max(ans1,min(b[i][j],r[i][j]-l[i][j]+1)); ans2=max(ans2,b[i][j]*(r[i][j]-l[i][j]+1)); } } int main(){ scanf("%d%d",&n,&m); rpt(i,1,n) rpt(j,1,m){ scanf("%d",&a[i][j]); if(i+j&1) a[i][j]=1-a[i][j]; } rpt(j,1,m) b[0][j]=b[0][n+1]-1; ans1=ans2=0; work(); rpt(i,1,n) rpt(j,1,m) a[i][j]=1-a[i][j]; work(); printf("%d\n%d\n",ans1*ans1,ans2); }