CodeVS1428 棋盘制作

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);
}

你可能感兴趣的:(CodeVS1428 棋盘制作)