P1169 [ZJOI2007]棋盘制作

题目描述

国际象棋是世界上最古老的博弈游戏之一,和中国的围棋、象棋以及日本的将棋同享盛名。据说国际象棋起源于易经的思想,棋盘是一个8×8大小的黑白相间的方阵,对应八八六十四卦,黑白对应阴阳。

而我们的主人公小Q,正是国际象棋的狂热爱好者。作为一个顶尖高手,他已不满足于普通的棋盘与规则,于是他跟他的好朋友小W决定将棋盘扩大以适应他们的新规则。

小Q找到了一张由N×M个正方形的格子组成的矩形纸片,每个格子被涂有黑白两种颜色之一。小Q想在这种纸中裁减一部分作为新棋盘,当然,他希望这个棋盘尽可能的大。

不过小Q还没有决定是找一个正方形的棋盘还是一个矩形的棋盘(当然,不管哪种,棋盘必须都黑白相间,即相邻的格子不同色),所以他希望可以找到最大的正方形棋盘面积和最大的矩形棋盘面积,从而决定哪个更好一些。

于是小Q找到了即将参加全国信息学竞赛的你,你能帮助他么?

输入输出格式

输入格式:

包含两个整数NM,分别表示矩形纸片的长和宽。接下来的NNN行包含一个N ×M01矩阵,表示这张矩形纸片的颜色(0表示白色,1表示黑色)。

输出格式:

包含两行,每行包含一个整数。第一行为可以找到的最大正方形棋盘的面积,第二行为可以找到的最大矩形棋盘的面积(注意正方形和矩形是可以相交或者包含的)。

输入输出样例

输入样例#1: 
3 3
1 0 1
0 1 0
1 0 0
输出样例#1: 
4
6

说明

对于20%的数据,N,M≤80

对于40%的数据,N,M≤400

对于100%的数据,N,M≤2000

Solution:

  本题dp。

  第一问是常规dp,第二问奇偶分出黑白点,再分别对黑点取反求下最大的全0或全1矩形,然后对全图取反再求一次取max就好了。

  这里主要安利一个很骚的做法,据说叫做悬线法。

  思路很简单,开三个数组:$lf[i][j]$表示$(i,j)$点往左最远的合法位置,同理$rf[i][j]$表示往右的最远合法位置、$up[i][j]$表示往上的最远合法位置,这些直接$n^2$预处理就好了。

  然后就是枚举每个位置,求出合法的长和宽,更新答案就好了(实现时的细节就是把$up[i][j]$在枚举位置的时候求,并把其含义改为合法宽度,对于每个宽度都应该求下合法面积)。

代码:

/*Code by 520 -- 9.20*/
#include<bits/stdc++.h>
#define il inline
#define ll long long
#define RE register
#define For(i,a,b) for(RE int (i)=(a);(i)<=(b);(i)++)
#define Bor(i,a,b) for(RE int (i)=(b);(i)>=(a);(i)--)
using namespace std;
const int N=2005;
int n,m,mp[N][N],lf[N][N],rf[N][N],up[N][N];
int ans1,ans2;

int main(){
    scanf("%d%d",&n,&m);
    For(i,1,n) For(j,1,m) scanf("%d",&mp[i][j]),lf[i][j]=rf[i][j]=j,up[i][j]=1;
    For(i,1,n) For(j,2,m) if(mp[i][j]^mp[i][j-1]) lf[i][j]=lf[i][j-1];
    For(i,1,n) Bor(j,1,m-1) if(mp[i][j]^mp[i][j+1]) rf[i][j]=rf[i][j+1];
    For(i,1,n) For(j,1,m) {
        if(i>1&&(mp[i][j]^mp[i-1][j])){
            lf[i][j]=max(lf[i][j],lf[i-1][j]);
            rf[i][j]=min(rf[i][j],rf[i-1][j]);
            up[i][j]=up[i-1][j]+1;
        }
        int c=rf[i][j]-lf[i][j]+1;
        int r=min(c,up[i][j]);
        ans1=max(ans1,r*r),ans2=max(ans2,c*up[i][j]);
    }
    cout<<ans1<<'
'<<ans2;
    return 0;
}
原文地址:https://www.cnblogs.com/five20/p/9688811.html