传送门 :P1169 [ZJOI2007] 棋盘制作 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
题目
国际象棋是世界上最古老的博弈游戏之一,和中国的围棋、象棋以及日本的将棋同享盛名。据说国际象棋起源于易经的思想,棋盘是一个 8×88×8 大小的黑白相间的方阵,对应八八六十四卦,黑白对应阴阳。
而我们的主人公 小Q
,正是国际象棋的狂热爱好者。作为一个顶尖高手,他已不满足于普通的棋盘与规则,于是他跟他的好朋友 小W
决定将棋盘扩大以适应他们的新规则。
小Q
找到了一张由 N×MN×M 个正方形的格子组成的矩形纸片,每个格子被涂有黑白两种颜色之一。小Q
想在这种纸中裁减一部分作为新棋盘,当然,他希望这个棋盘尽可能的大。
不过 小Q
还没有决定是找一个正方形的棋盘还是一个矩形的棋盘(当然,不管哪种,棋盘必须都黑白相间,即相邻的格子不同色),所以他希望可以找到最大的正方形棋盘面积和最大的矩形棋盘面积,从而决定哪个更好一些。
于是 小Q
找到了即将参加全国信息学竞赛的你,你能帮助他么?
输入格式
包含两个整数 NN 和 MM,分别表示矩形纸片的长和宽。接下来的 NN 行包含一个 N×MN×M 的 0101 矩阵,表示这张矩形纸片的颜色(00 表示白色,11 表示黑色)。
输出格式
包含两行,每行包含一个整数。第一行为可以找到的最大正方形棋盘的面积,第二行为可以找到的最大矩形棋盘的面积(注意正方形和矩形是可以相交或者包含的)。
数据范围
对于所有数据,N, M ≤ 2000
题解
运用悬线法
- 初始化四个辅助数组:
l[i][j]
:表示以(i, j)
为右下角的矩形中,能向左延伸的最远列的索引。r[i][j]
:表示以(i, j)
为右下角的矩形中,能向右延伸的最远列的索引。h[i][j]
:表示以(i, j)
为右下角的矩形的高度。ans1
和ans2
用于存储最大矩形和最大正方形的面积,初始值均为0。
1. 计算左右边界
-
左边界(
l
):从第二列开始,对每一行进行遍历。如果当前单元格G[i][j]
的值与左侧单元格G[i][j-1]
不同,则当前单元格的左边界等于左侧单元格的左边界。 -
右边界(
r
):从倒数第二列开始,同样对每一行进行遍历。如果当前单元格G[i][j]
的值与右侧单元格G[i][j+1]
不同,则当前单元格的右边界等于右侧单元格的右边界。
2. 计算高度和更新结果
-
遍历每个单元格
(i, j)
,如果不是第一行并且上方单元格G[i-1][j]
的值与当前单元格的值不同,说明两行之间有不同的值:- 更新当前单元格的高度为上方单元格的高度 + 1。
- 更新左边界和右边界,取当前行和上方行的左(或右)边界的最大(或最小)值。
-
最大矩形面积 (
ans1
):计算以(i, j)
为右下角的矩形面积,面积计算方式为(右边界 - 左边界 + 1) * 高度
。将计算得到的面积与当前的ans1
进行比较,更新ans1
为更大的值。 -
最大正方形面积 (
ans2
):通过取最大正方形的边长,更新面积的计算方式为min(右边界 - 左边界 + 1, 高度)
。然后将这个正方形面积与当前的ans2
比较并更新。
#include <iostream>
using namespace std;
const int MAXN = 2e3 + 5;
int n, m;
// 用于存储高度、左右边界和原始输入网格的数组
int h[MAXN][MAXN], l[MAXN][MAXN], r[MAXN][MAXN], G[MAXN][MAXN];
// 存储最终答案的变量
int ans1 = 0, ans2 = 0;
// 执行主要逻辑的函数
void fun() {
// 输入网格并初始化左、右边界和高度数组
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
cin >> G[i][j]; // 读取输入网格
l[i][j] = r[i][j] = j; // 初始化左右边界为当前列
h[i][j] = 1; // 初始高度设为1
}
}
// 计算左边界
for (int i = 1; i <= n; i++) {
for (int j = 2; j <= m; j++) {
// 如果当前单元格与左侧单元格不同,更新左边界
if (G[i][j] != G[i][j - 1])
l[i][j] = l[i][j - 1];
}
// 计算右边界
for (int j = m - 1; j >= 1; j--) {
// 如果当前单元格与右侧单元格不同,更新右边界
if (G[i][j] != G[i][j + 1])
r[i][j] = r[i][j + 1];
}
}
// 计算高度及最终结果
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
// 如果不是第一行,并且上面单元格的值不同
if (i > 1 && G[i - 1][j] != G[i][j]) {
h[i][j] = h[i - 1][j] + 1; // 增加高度
l[i][j] = max(l[i][j], l[i - 1][j]); // 更新左边界基于上方的行
r[i][j] = min(r[i][j], r[i - 1][j]); // 更新右边界基于上方的行
}
// 计算当前高度和宽度的矩形面积,用于 ans1(最大矩形面积)
ans1 = max(ans1, (r[i][j] - l[i][j] + 1) * h[i][j]); // 根据当前高度和宽度计算面积
int minn = min(r[i][j] - l[i][j] + 1, h[i][j]); // 为了 ans2 计算的最小边长
ans2 = max(ans2, minn * minn); // 更新 ans2 以获取最大正方形面积
}
}
}
int main() {
cin >> n >> m; // 读取网格的维度
fun(); // 调用处理网格的函数
cout << ans2 << '\n' << ans1; // 打印最大正方形面积和最大矩形面积
return 0; // 程序结束
}