[ZJOI2007]棋盘制作
ZJOI2007棋盘制作
Description
国际象棋是世界上最古老的博弈游戏之一,和中国的围棋、象棋以及日本的将棋同享盛名。据说国际象棋起源
于易经的思想,棋盘是一个8*8大小的黑白相间的方阵,对应八八六十四卦,黑白对应阴阳。而我们的主人公小Q,
正是国际象棋的狂热爱好者。作为一个顶尖高手,他已不满足于普通的棋盘与规则,于是他跟他的好朋友小W决定
将棋盘扩大以适应他们的新规则。小Q找到了一张由N*M个正方形的格子组成的矩形纸片,每个格子被涂有黑白两种
颜色之一。小Q想在这种纸中裁减一部分作为新棋盘,当然,他希望这个棋盘尽可能的大。不过小Q还没有决定是找
一个正方形的棋盘还是一个矩形的棋盘(当然,不管哪种,棋盘必须都黑白相间,即相邻的格子不同色),所以他
希望可以找到最大的正方形棋盘面积和最大的矩形棋盘面积,从而决定哪个更好一些。于是小Q找到了即将参加全
国信息学竞赛的你,你能帮助他么?
Input
第一行包含两个整数N和M,分别表示矩形纸片的长和宽。接下来的N行包含一个N * M的01矩阵,表示这张矩形
纸片的颜色(0表示白色,1表示黑色)。
Output
包含两行,每行包含一个整数。第一行为可以找到的最大正方形棋盘的面积,第二行为可以找到的最大矩形棋
盘的面积(注意正方形和矩形是可以相交或者包含的)。
Sample Input
3 3
1 0 1
0 1 0
1 0 0
Sample Output
4
6
HINT
N, M ≤ 2000
Solution
这道题总体来说应该不是一道思维难度特别大的DP,因为n和m的范围并不是那么大,所以只需要想出来一种nm的算法就可以了。
题解里有很多单调栈的写法,其实用不着,也有很多奇奇怪怪比较难懂的解法,笔者表示虽然做出来这个题,但还是看不懂他们的题解。。。那么我们需要一种性价比比较高的方法来实现这道题目。
可以发现的是,这个题目的矩阵里的数字只有0和1两种状态,所以我们可以考虑一下使用前缀的思想和min的思想实现这道题目。
我们先用\(l[i][j]\)来表示从\((i,j)\)这个点向左延展最多能有的棋盘的宽度,用\(r[i][j]\)来表示向右延展能有的宽度,这么一来我们就可以用\(r[i][j]+l[i][j]-1\)直接算出当前能有的宽度。
然后我们用\(h[i][j]\)来表示\((i,j)\)这个点能有的最大的高度,同时我们限制上面的l和r数组表示为到当前这一行向左向右延展的最大宽度。注意:这个地方不能简单的看完跳过去了,这里留下几个问题留待读者自己根据代码发掘。第一个就是为什么可以这么写,因为我们后面要使用\((h[i][j]+1)\times (r[i][j]+l[i][j]-1)\)来计算最大矩形的面积,那么此时的h一定就是在我们想要的那个宽度的范围内么?第二个就是我们从第一行向下进行递推,那么我们的l和r数组在第一行都相同也就是都为1的情况下,能够找到下面的最大矩形么?
这两个问题解决了之后,这个题也就没什么难点了。
Code
#pragma comment(linker, "/STACK:1024000000,1024000000")
#include <iostream>
#include <cstdlib>
#include <cmath>
#include <string>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <queue>
#include <set>
#include <map>
#define re register
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
#define ms(arr) memset(arr, 0, sizeof(arr))
const int inf = 0x3f3f3f3f;
int l[2001][2001],r[2001][2011],h[2001][2001],f[2001],a[2001][2001],n,m,ans,sum;
inline int read()
{
int x=0,c=1;
char ch=' ';
while((ch>'9'||ch<'0')&&ch!='-')ch=getchar();
while(ch=='-') c*=-1,ch=getchar();
while(ch<='9'&&ch>='0')x=x*10+ch-'0',ch=getchar();
return x*c;
}
int main()
{
//freopen("date.in","r",stdin);
n=read();m=read();
for(re int i=1;i<=n;i++)
for(re int j=1;j<=m;j++){
a[i][j]=read();
}
for(re int i=1;i<=n;i++){
for(re int j=1;j<=m;j++)
if(a[i][j]!=a[i][j-1]) l[i][j]=l[i][j-1]+1;
else l[i][j]=1;
for(re int j=m;j>=1;j--)
if(a[i][j]!=a[i][j+1]) r[i][j]=r[i][j+1]+1;
else r[i][j]=1;
}
for(re int i=2;i<=n;i++)
for(re int j=1;j<=m;j++){
if(a[i][j]!=a[i-1][j]){
h[i][j]=h[i-1][j]+1;
l[i][j]=min(l[i][j],l[i-1][j]);
r[i][j]=min(r[i][j],r[i-1][j]);
}
}
for(re int i=1;i<=n;i++){
for(re int j=1;j<=m;j++){
int E=min(h[i][j]+1,r[i][j]+l[i][j]-1);
ans=max(ans,E*E);
sum=max(sum,(h[i][j]+1)*(r[i][j]+l[i][j]-1));
}
}
cout<<ans<<endl<<sum;
return 0;
}
/*
5 5
1 1 1 1 1
1 0 1 0 1
1 1 0 1 0
1 0 1 0 1
1 1 1 1 1
*/
附赠一组数据验证上面想让读者思考的两个点。