当前位置: 首页 > news >正文

GESP2025年9月认证C++四级真题与解析(编程题1(排兵布阵))

一、先看原题


二、题目解析

1、《在方格王国里找最大草坪》

(1)想象这样一个世界 🏰:

  • 这是一块方格王国

  • 每个格子:

    • 1= 🌱 草地(可以建房)

    • 0= 🌋 火山(不能建)

我们的任务是:

🏡 找一块全是草地的最大长方形区域
给国王建一个大房子!


2、例如这样一张“地图” 🗺️

一个4 × 5 的地图

行\列 1 2 3 4 5 1 1 1 0 1 1 2 1 1 0 1 1 3 1 1 1 1 1 4 0 1 1 1 0

3、最笨但最安全的办法是什么?🤔

🤷‍♂️不知道哪里最大,那就全部试一遍!

这就是我们首先想的方法:

👉枚举所有可能的矩形


4、如何“枚举所有矩形”?🧠

(1)一个矩形,需要4 个信息

左上角:(x1, y1) 右下角:(x2, y2)

(2)如果两个矩形的左上角相同,右下角也相同,这就是相同的矩形。

换句话说,如果两个矩形,只要左上角与右上角有一个不相同,就是不同的矩形


(3)所以我们枚举所有矩形:

1️⃣ 选左上角
2️⃣ 选右下角
3️⃣ 根据左上角,与右上角,枚举这个矩形内部的所有点,看看有不是全部都是1

4️⃣ 如果全部都是1,就与前面枚举的矩形面积比大小,更新max

👉 这就是使用最朴素枚举方法来解决这个问题


5、参考程序:

#include <iostream> using namespace std; int a[25][25]; int n, m; int main() { cin >> n >> m; // 读入矩阵 for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { cin >> a[i][j]; } } int ans = 0; // ① 枚举左上角行 for (int x1 = 1; x1 <= n; x1++) { // ② 枚举左上角列 for (int y1 = 1; y1 <= m; y1++) { // ③ 枚举右下角行 for (int x2 = x1; x2 <= n; x2++) { // ④ 枚举右下角列 for (int y2 = y1; y2 <= m; y2++) { bool ok = true; // ⑤ 枚举矩形内部行 for (int i = x1; i <= x2; i++) { // ⑥ 枚举矩形内部列 for (int j = y1; j <= y2; j++) { if (a[i][j] == 0) { ok = false; } } } // 如果这个矩形全是 1 if (ok) { int area = (x2 - x1 + 1) * (y2 - y1 + 1); ans = max(ans, area); } } } } } cout << ans << endl; return 0; }

6、时间复杂度符合吗?

(1)先说结论

✅ 本题数据范围,n,m < 12,是可以通过的


(2)矩阵大小是:

n × m

那么

循环次数
左上角n × m
右下角n × m
检查内部n × m

(3)👉 总复杂度大约是:

O(n² · m² · n · m) = O(n³ · m³)

三、高阶提升:

🧠如何对朴素枚举程序进行优化呢?

(一)、使用二维前缀预处理,可以减少矩形合法性计算时间!

二维前缀和不能减少“枚举矩形的次数”,
但可以把“检查一个矩形是否合法”的复杂度
从 O(面积) 降到 O(1)


1、算法思路

原始朴素暴力(问题在哪)

  • 每次:再扫一遍矩形内部,看是不是全 1

👉慢在“反复扫描矩形内部”


2、用二维前缀和之后

1️⃣预处理一个前缀和数组sum
2️⃣ 枚举所有矩形
3️⃣O(1) 判断矩形里 1 的个数


3、参考程序:

#include <iostream> using namespace std; int a[20][20]; int sum[20][20]; int main() { int n, m; cin >> n >> m; // 读入矩阵 for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { cin >> a[i][j]; } } // 1️⃣ 计算二维前缀和 for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { sum[i][j] = sum[i-1][j] + sum[i][j-1] - sum[i-1][j-1] + a[i][j]; } } int ans = 0; // 2️⃣ 枚举所有矩形 for (int x1 = 1; x1 <= n; x1++) { for (int x2 = x1; x2 <= n; x2++) { for (int y1 = 1; y1 <= m; y1++) { for (int y2 = y1; y2 <= m; y2++) { // 用前缀和 O(1) 计算矩形内 1 的个数 int ones = sum[x2][y2] - sum[x1-1][y2] - sum[x2][y1-1] + sum[x1-1][y1-1]; int area = (x2 - x1 + 1) * (y2 - y1 + 1); // 如果全是 1,更新答案 if (ones == area) { ans = max(ans, area); } } } } } cout << ans << endl; return 0; }

4、时间复杂度

部分复杂度
前缀和预处理O(nm)
枚举矩形O(n²m²)
矩形合法性判断O(1)

👉总复杂度:O(n²m²)


(二)、固定上下边 + 压成一维(状态压缩)


1、算法一句话总览🎯

固定矩形的“上边”和“下边”,
把二维矩阵压缩成一维数组,
再在一维数组中找“最长连续 1 段”。


2、为什么这样能减少循环?

(1)原来(4 重循环)在枚举什么?

上边 + 下边 + 左边 + 右边

👉 你是在枚举“矩形本身”


(2)现在(固定上下边)枚举什么?

上边 + 下边 + 一次横向扫描

👉 是在枚举“高度”,横向找宽度


(3)本质变化

❌ 枚举矩形
✅ 枚举“高度 + 连续宽度”


3、核心思想拆解

1️⃣ 固定上下边

for (int top = 1; top <= n; top++) { for (int bottom = top; bottom <= n; bottom++) { ... } }

👉 确定了矩形的高度

height = bottom - top + 1;

2️⃣ 对每一列,判断“这一整列能不能用”

对某一列j

  • 如果top ~ bottom这一列全是 1
    👉 这一列是1

  • 只要有一个0
    👉 这一列是0

这样就得到一个一维 0/1 数组


3️⃣ 一维问题变成什么?

在 0/1 数组中,找最长连续的 1

我们已经非常熟悉了:

if (col[j] == 1) cnt++; else cnt = 0;

4️⃣ 面积怎么计算?

面积 = 连续列数 * 高度

4、【固定上下边 + 压成一维】完整参考程序

#include <iostream> using namespace std; int a[20][20]; int main() { int n, m; cin >> n >> m; // 读入矩阵 for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { cin >> a[i][j]; } } int ans = 0; // 1️⃣ 固定上边 for (int top = 1; top <= n; top++) { // col[j] 表示:当前 top 到 bottom 之间, // 第 j 列是否全是 1 int col[20] = {0}; // 2️⃣ 枚举下边 for (int bottom = top; bottom <= n; bottom++) { // 更新每一列是否仍然“可用” for (int j = 1; j <= m; j++) { if (bottom == top) { // 第一行,直接赋值 col[j] = a[bottom][j]; } else { // 只要出现 0,这一列就废了 col[j] = col[j] && a[bottom][j]; } } // 当前矩形高度 int height = bottom - top + 1; // 3️⃣ 在 col[] 中找最长连续 1 int cnt = 0; for (int j = 1; j <= m; j++) { if (col[j] == 1) { cnt++; ans = max(ans, cnt * height); } else { cnt = 0; } } } } cout << ans << endl; return 0; }

5、时间复杂度分析

部分复杂度
枚举 top, bottomO(n²)
每次扫描列O(m)

👉总复杂度:O(n² · m)
👉 比前缀和的O(n² · m²)明显更快


http://www.jsqmd.com/news/275572/

相关文章:

  • 高频去耦电容配置方法:操作指南(含实例)
  • 超详细版SystemVerilog随机测试生成技术深度剖析
  • 28.C++进阶:map和set封装|insert|迭代器|[]
  • 大数据时代,Power BI 成为数据洞察的关键工具
  • vivado2021.1安装教程:满足工控高可靠性要求的方法
  • 基于MAX3232的RS232接口引脚定义调试技巧
  • 计算机毕业设计springboot易耗品管理系统 基于SpringBoot的企业低值易耗品智能管理平台 SpringBoot驱动的办公耗材全流程管控系统
  • 计算机毕业设计springboot飞机票预定系统 基于SpringBoot的航空客运订票平台设计与实现 融合Vue+SpringBoot的在线航班座位预约系统
  • Pspice安装教程:从下载到运行的全面讲解
  • 教学思考(3)
  • 计算机毕业设计springboot乡镇人口信息管理系统 基于SpringBoot的乡镇居民信息综合管理平台 面向基层治理的SpringBoot人口大数据服务系统
  • 打造智能化 ECS 故障分析 Agent:从创建到实战
  • Altium原理图与FPGA引脚规划协同设计实践
  • 数字频率计设计:FPGA开发环境配置指南
  • emuelec固件升级注意事项:安全更新操作指南
  • 组合逻辑电路设计入门必看:基本概念与实例解析
  • SkyWalking 接口超时监控告警完整指南
  • 图解说明三极管开关电路:基础结构与信号流向
  • 汇编语言全接触-99.检测内存中的 Soft-Ice
  • Prim 最小生成树算法(MST)
  • vivado2023.2安装步骤详解:FPGA开发环境从零搭建
  • 全球物流业进入“退货季“,女士连衣裙退货率接近90%
  • 超详细版MOSFET开关时序分析及其工作原理
  • 有没有推荐的汽车自动化生产系统或智能解决方案?
  • 深度剖析树莓派apt-update出错的根源与修复方法
  • 污水流量监测之多普勒超声波流量计应用技术分析
  • 深度剖析LED驱动电路启动过程与响应特性
  • ARM体系结构
  • 回首 jQuery 20 年:从辉煌到没落
  • 汇编语言全接触-100.拾取密码框中的密码