BZOJ1057【ZJOI2007】棋盘制作 < 悬线法DP >

Problem

【ZJOI2007】棋盘制作

Time  Limit:  20  Sec\mathrm{Time\;Limit:\;20\;Sec}
Memory  Limit:  162  MB\mathrm{Memory\;Limit:\;162\;MB}

Description

国际象棋是世界上最古老的博弈游戏之一,和中国的围棋、象棋以及日本的将棋同享盛名。据说国际象棋起源于易经的思想,棋盘是一个8×88\times8大小的黑白相间的方阵,对应八八六十四卦,黑白对应阴阳。而我们的主人公Q\mathrm{小Q},正是国际象棋的狂热爱好者。作为一个顶尖高手,他已不满足于普通的棋盘与规则,于是他跟他的好朋友W\mathrm{小W}决定将棋盘扩大以适应他们的新规则。
Q\mathrm{小Q}找到了一张由N×MN\times M个正方形的格子组成的矩形纸片,每个格子被涂有黑白两种颜色之一。Q\mathrm{小Q}想在这种纸中裁减一部分作为新棋盘,当然,他希望这个棋盘尽可能的大。不过Q\mathrm{小Q}还没有决定是找一个正方形的棋盘还是一个矩形的棋盘(当然,不管哪种,棋盘必须都黑白相间,即相邻的格子不同色),所以他希望可以找到最大的正方形棋盘面积和最大的矩形棋盘面积,从而决定哪个更好一些。于是Q\mathrm{小Q}找到了即将参加全国信息学竞赛的你,你能帮助他么?

Input

第一行包含两个整数NNMM,分别表示矩形纸片的长和宽。
接下来的NN行包含一个N×MN\times M0101矩阵,表示这张矩形纸片的颜色(00表示白色,11表示黑色)。

Output

包含两行,每行包含一个整数。
第一行为可以找到的最大正方形棋盘的面积;
第二行为可以找到的最大矩形棋盘的面积。

Sample Input

1
2
3
4
3 3
1 0 1
0 1 0
1 0 0

Sample Output

1
2
4
6

HINT

N,M2000N,M\le2000

标签:悬线法DP

Solution

经典悬线法DP\mathrm{DP}

sij,tijs_{ij},t_{ij}表示从(i,j)(i,j)向左/右以0101交错的形式最远能延长到的位置,显然这两个数组可以O(nm)O(nm)预处理。
对于位置(i,j)(i,j),用lijl_{ij}表示从(i,j)(i,j)向上走,最多以0101交错的形式走多远,显然也是可以O(nm)O(nm)预处理的。而由这个值可知ailij+1,jai,ja_{i-l_{ij}+1,j}\sim a_{i,j}0101交错的形式,若以其为棋盘的高,则宽为

mink=ilij+1i{tkj}maxk=ilij+1i{skj}\min_{k=i-l_{ij}+1}^{i}\lbrace t_{kj}\rbrace-\max_{k=i-l_{ij}+1}^{i}\lbrace s_{kj}\rbrace

那么在预处理lijl_{ij}的同时,我们对可以连在一起的横行求tt的最小值和ss的最大值,即若ai,jai1,ja_{i,j}\ne a_{i-1,j},则

sij=max(sij,si1  j)tij=min(tij,ti+1  j)s_{ij}=\max(s_{ij},s_{i-1\;j})\\ t_{ij}=\min(t_{ij},t_{i+1\;j})\\

最后枚举每个位置,求以其所在纵行为起点向左右扩展的最大矩形即可得到答案。
对于最大正方形,易知其由宽与高的最小值最大的矩形裁截而来,于是对每个极大矩形求其包含的最大正方形并取最大值即可。

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <bits/stdc++.h>
using namespace std;
template <class T> inline void read(T &x) {
x = 0; int c = getchar(), f = 1;
for (; !isdigit(c); c = getchar()) if (c == 45) f = -1;
for (; isdigit(c); c = getchar()) (x *= 10) += f*(c-'0');
}
int n, m, mx1, mx2, a[2005][2005];
int s[2005][2005], t[2005][2005], l[2005][2005];
int main() {
read(n), read(m);
for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++)
read(a[i][j]), s[i][j] = t[i][j] = j, l[i][j] = 1;
for (int i = 1; i <= n; i++) {
for (int j = 2; j <= m; j++)
if (a[i][j]^a[i][j-1]) s[i][j] = s[i][j-1];
for (int j = m-1; j; j--)
if (a[i][j]^a[i][j+1]) t[i][j] = t[i][j+1];
}
for (int i = 2; i <= n; i++)
for (int j = 1; j <= m; j++)
if (a[i][j]^a[i-1][j])
l[i][j] += l[i-1][j],
s[i][j] = max(s[i][j], s[i-1][j]),
t[i][j] = min(t[i][j], t[i-1][j]);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++) {
int r = l[i][j], c = t[i][j]-s[i][j]+1;
mx1 = max(mx1, min(r, c)*min(r, c));
mx2 = max(mx2, r*c);
}
return printf("%d\n%d\n", mx1, mx2), 0;
}