【洛谷】【ZJOI2007】棋盘制作(悬线法)

传送门 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; // 程序结束  
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值