[ZJOI2007]棋盘制作

Posted by Panda2134's Blog on August 20, 2017

链接:Luogu-P1169

题意

在一个$N \times M$的矩阵内,找出最大的、像国际象棋棋盘一样黑白交错的正方形和矩形。

思路

看到第一问,显然可以用二维平面DP完成:考虑设计状态$f(i,j,k)$:以$(i,j)$为右下角的最大正方形。k=1表示在这个正方形中$(i,j)$为黑,反之$(i,j)$为白。因为要求黑白交错,所以把黑白也加入状态。
于是可以转移:
$f(i,j,k)=\min \left\{f(i-1,j-1,k),f(i-1,j,\neg k),f(i,j-1,\neg k)\right\}+1$
但是第二问就不好应用二维平面DP完成了,因为一般来说二维平面DP只用于正方形。
有这样一个技巧:把横纵坐标奇偶性不同的点颜色取反,然后用悬线法求最大子矩形就行了。同理,还原成原来的矩阵后,再把横纵坐标奇偶性相同的点颜色取反,然后再做一遍悬线法,即可得到1、2问的答案。

实现

#include <cstdio>
#include <cstring>
#include <cctype>
#include <algorithm>
#define CLEAR(x) memset(x,0,sizeof(x))
#define SETINF(x) memset(x,0x3f,sizeof(x))
#define pow2(x) ((x)*(x))
using namespace std;
const int MAXN = 2000;
int N,M,Ans1,Ans2,W[MAXN+10][MAXN+10],lc[MAXN+10][MAXN+10],rc[MAXN+10][MAXN+10],
l[MAXN+10][MAXN+10],r[MAXN+10][MAXN+10],h[MAXN+10][MAXN+10];
inline int readint(){
	int f=1,r=0;char c=getchar();
	while(!isdigit(c)){if(c=='-')f=-1; c=getchar();}
	while(isdigit(c)){r=r*10+c-'0';c=getchar();}
	return f*r;
}
inline void work(){
	CLEAR(lc);CLEAR(rc);CLEAR(h);SETINF(l);SETINF(r);
	for(int i=1;i<=N;i++)
		for(int j=1;j<=M;j++)
			lc[i][j]=(W[i][j]?0:lc[i][j-1]+1);
	for(int i=1;i<=N;i++)
		for(int j=M;j>=1;j--)
			rc[i][j]=(W[i][j]?0:rc[i][j+1]+1);
	for(int i=1;i<=N;i++)
		for(int j=1;j<=M;j++)
			h[i][j]=(W[i][j]?0:h[i-1][j]+1);
	for(int i=1;i<=N;i++)
		for(int j=1;j<=M;j++) 
			if(!W[i][j]){
				l[i][j]=min(l[i-1][j],lc[i][j]);
				r[i][j]=min(r[i-1][j],rc[i][j]);
			}
	for(int i=1;i<=N;i++)
		for(int j=1;j<=M;j++) {
			Ans1=max(Ans1, pow2( min(r[i][j]+l[i][j]-1, h[i][j]) ));
			Ans2=max(Ans2, (r[i][j]+l[i][j]-1)*h[i][j]);
		}
}
int main(){
	N=readint();M=readint();
	for(int i=1;i<=N;i++)
		for(int j=1;j<=M;j++)
			W[i][j]=readint();
	for(int i=1;i<=N;i++)
		for(int j=1;j<=M;j++)
			W[i][j]^=((i^j)&1);
	work();
	for(int i=1;i<=N;i++)
		for(int j=1;j<=M;j++) {
			W[i][j]^=((i^j)&1);
			W[i][j]^=(~((i^j)&1))&1;
		}
	work();
	printf("%d\n%d",Ans1,Ans2);
}