202406-黑白格(luogu-P10719)

202406-黑白格(luogu-P10719)

GESP C++ 2024年6月五级真题,前缀和思想考点,难度⭐⭐⭐☆☆,属于五级真题中比较简单的。洛谷难度等级普及/提高−

luogu-P10719 [GESP202406 五级] 黑白格

题目要求

题目描述

小杨有一个 nnmm 列的网格图,其中每个格子要么是白色,要么是黑色。

小杨想知道至少包含 kk 个黑色格子的最小子矩形包含了多少个格子。

输入格式

第一行包含三个正整数 n,m,kn,m,k,含义如题面所示。

之后 nn 行,每行⼀个长度为 mm01\texttt{01} 串,代表网格图第 ii 行格子的颜色,如果为 0\texttt{0},则对应格子为白色,否则为黑色。

输出格式

输出一个整数,代表至少包含 kk 个黑色格子的最小子矩形包含格子的数量,如果不存在则输出 00

输入输出样例 #1

输入 #1
4 5 5
00000
01111
00011
00011
输出 #1
6

说明/提示

样例解释

对于样例 11,假设 (i,j)(i,j) 代表第 ii 行第 jj 列,至少包含 55 个黑色格子的最小子矩形的四个顶点为 (2,4)(2,4)(2,5)(2,5)(4,4)(4,4)(4,5)(4,5),共包含 66 个格子。

数据范围

对于全部数据,保证有 1n,m1001\le n,m\le 1001kn×m1\le k\le n\times m

子任务编号得分n,mn,m
11202010\le 10
224040n=1n=11m1001\le m\le 100
334040100\le 100

题目分析

解题思路

  1. 二维前缀和——把“数黑格”变成 O(1)O(1) 查询
    先扫一遍网格,用
    pre[i][j] = pre[i-1][j] + pre[i][j-1] - pre[i-1][j-1] + (s[i-1][j-1]=='1')
    把以 (1,1)(1,1) 为左上角、(i,j)(i,j) 为右下角的矩形黑格总数存下来。
    之后任意子矩形黑格数只需一次加减即可得到,为后面暴力枚举省下大量时间。

  2. 四重循环——暴力但足够快
    固定左上角 (i,j)(i,j),右下角 (l,r)(l,r)(i,j)(i,j) 开始向右下“拖”矩形。
    一旦黑格数 k\ge k,立刻用 (li+1)×(rj+1)(l-i+1)\times(r-j+1) 更新当前最小面积。

  3. 无解处理
    若跑完所有矩形都没出现 k\ge k 的情况,答案输出 00;否则输出记录到的最小面积。

复杂度
时间:O(n2m2)108O(n^2m^2)\approx 10^8,空间:O(nm)O(nm) 前缀和数组。

本题是五级“前缀和 + 暴力枚举”标准套路,六层循环纯暴力会超时。


示例代码

#include <iostream>
#include <vector>

// 前缀和数组,pre_sum[i][j] 表示从 (1,1) 到 (i,j) 的矩形中黑色格子的总数
int pre_sum[105][105];

int main() {
    int n, m, k;
    std::cin >> n >> m >> k;          // 读入行数、列数、至少需要的黑色格子数 k

    std::vector<std::string> strs(n);
    for (int i = 0; i < n; i++) {
        std::cin >> strs[i];          // 读入每行的 01 串,'1' 表示黑色格子
    }

    // 计算二维前缀和,注意下标从 1 开始,方便边界处理
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            // 计算二维前缀和:pre_sum[i+1][j+1] 表示从 (1,1) 到 (i+1,j+1) 矩形内黑色格子总数
            // 递推公式:当前矩形和 = 上方矩形和 + 左方矩形和 - 左上重叠矩形和 + 当前格子值
            // 这样即可 O(1) 得到任意子矩形和,为后续四重循环快速判断提供基础
            pre_sum[i + 1][j + 1] = pre_sum[i][j + 1] + pre_sum[i + 1][j] - pre_sum[i][j]
                                  + (strs[i][j] == '1' ? 1 : 0);
        }
    }

    bool flag = false;                // 标记是否找到满足条件的子矩形
    int min_count = n * m;            // 初始化最小格子数为整个网格大小

    // 四重循环枚举所有可能的子矩形
    // (i,j) 为子矩形左上角,(l,r) 为右下角
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            for (int l = i; l <= n; l++) {
                for (int r = j; r <= m; r++) {
                    // 利用前缀和快速计算当前子矩形内的黑色格子数
                    // 利用二维前缀和公式:子矩形 (i..l, j..r) 的黑色格子数
                    // = 右下角大矩形和 - 上方矩形和 - 左侧矩形和 + 左上角重叠矩形和
                    int cur_count = pre_sum[l][r] - pre_sum[i - 1][r]
                                  - pre_sum[l][j - 1] + pre_sum[i - 1][j - 1];
                    if (cur_count >= k) {
                        // 更新最小格子数
                        min_count = std::min(min_count, (l - i + 1) * (r - j + 1));
                        flag = true;
                    }
                }
            }
        }
    }

    // 输出结果,若未找到则输出 0
    if (flag) {
        std::cout << min_count << std::endl;
    } else {
        std::cout << 0 << std::endl;
    }
    return 0;
}

本文由coderli.com原创,按照CC BY-NC-SA 4.0 进行授权

所有代码已上传至Github:https://github.com/lihongzheshuai/yummy-code

luogu-”系列题目可在 洛谷题库 在线评测。

bcqm-”系列题目可在 编程启蒙题库 在线评测。

GESP/CSP认证交流QQ群: 688906745

GESP/CSP 认证学习微信公众号
GESP/CSP 认证学习微信公众号
最后更新于