【2024年莆田市校园创客节(小学组)初赛】泡泡堂
泡泡堂
题目描述
问题描述
在 A×B 大小的地图上,BOSS 同时投放 N 个泡泡。每个泡泡爆炸后,冲击波会向上下左右四个方向十字扩散,扩散长度为 L(包含泡泡所在格子)。请计算地图中不会被任何泡泡炸到的安全格子数量。
输入格式
- 第一行:三个正整数 A、B、N(分别表示地图行数、列数、泡泡数量);
- 接下来 N 行:每行三个正整数 Xᵢ、Yᵢ、Lᵢ(分别表示第 i 个泡泡的行数、列数、扩散长度)。
输出格式
一个整数,表示地图中安全格子的数量。
样例输入输出
样例输入 1
6 6 3
2 2 3
4 4 4
3 5 6
样例输出 1
12
样例输入 2
4 5 2
2 2 1
4 2 5
样例输出 2
12
数据范围
- 30% 数据:1≤A,B≤50,1≤N≤10,1≤Lᵢ≤50;
- 100% 数据:1≤A,B≤20000,1≤A×B≤40000(关键限制!总格子数不超过 4 万),1≤N≤1000,1≤Xᵢ≤A,1≤Yᵢ≤B,1≤Lᵢ≤20000。
解题思路
核心分析
题目本质是 标记被泡泡爆炸覆盖的格子,最后统计未被标记的格子数。关键突破口是数据范围中的 A×B≤40000 —— 虽然 A 和 B 单个值可能很大(达 2 万),但总格子数有限,直接用二维数组标记覆盖状态完全可行,无需复杂优化。
思路拆解
- 初始化地图状态:创建二维数组
map,用1表示安全格子,0表示被覆盖格子,初始时所有格子均为1(默认安全); - 处理每个泡泡的覆盖范围:
- 水平方向(左右扩散):泡泡在 (X, Y),扩散长度 L,水平覆盖范围是 Y-L+1 到 Y+L-1。需确保范围在 [1, B] 内(不超出地图列数),将该范围内 (X, j) 格子标记为
0; - 垂直方向(上下扩散):垂直覆盖范围是 X-L+1 到 X+L-1。需确保范围在 [1, A] 内(不超出地图行数),将该范围内 (j, Y) 格子标记为
0;
- 水平方向(左右扩散):泡泡在 (X, Y),扩散长度 L,水平覆盖范围是 Y-L+1 到 Y+L-1。需确保范围在 [1, B] 内(不超出地图列数),将该范围内 (X, j) 格子标记为
- 统计安全格子:遍历整个地图,计数仍为
1的格子数量,即为答案。
关键细节
- 坐标边界处理:扩散范围不能超出地图(比如泡泡在第 2 行,L=3,向上扩散不能到第 -1 行,需取最小值 1),用
max和min函数限制范围; - 重复覆盖无需处理:多个泡泡覆盖同一格子,只需标记一次
0,后续重复标记不影响结果; - 数组下标设计:地图行和列从 1 开始(与输入坐标一致),避免下标转换错误。
代码实现
#include <bits/stdc++.h>
using namespace std;int main() {int A, B, N;cin >> A >> B >> N;// 地图数组:map[i][j] = 1 表示安全,0 表示被覆盖;下标从1开始,适配输入坐标int map[A + 5][B + 5]; // 额外+5避免边界溢出(冗余设计,更安全)// 步骤1:初始化地图,所有格子默认安全(1)for (int i = 1; i <= A; ++i) {for (int j = 1; j <= B; ++j) {map[i][j] = 1;}}// 步骤2:处理每个泡泡的覆盖范围for (int i = 0; i < N; ++i) {int X, Y, L;cin >> X >> Y >> L;// 1. 水平方向覆盖(左右扩散):行固定为X,列范围 [Y-L+1, Y+L-1]int left = max(Y - L + 1, 1); // 左边界不小于1int right = min(Y + L - 1, B); // 右边界不大于Bfor (int j = left; j <= right; ++j) {map[X][j] = 0; // 标记为被覆盖}// 2. 垂直方向覆盖(上下扩散):列固定为Y,行范围 [X-L+1, X+L-1]int top = max(X - L + 1, 1); // 上边界不小于1int bottom = min(X + L - 1, A); // 下边界不大于Afor (int j = top; j <= bottom; ++j) {map[j][Y] = 0; // 标记为被覆盖}}// 步骤3:统计安全格子数量(值为1的格子)int safe_count = 0;for (int i = 1; i <= A; ++i) {for (int j = 1; j <= B; ++j) {if (map[i][j] == 1) {safe_count++;}}}cout << safe_count << endl;return 0;
}
代码解释
变量与数组说明
A, B, N:分别存储地图行数、列数、泡泡数量;map[A+5][B+5]:二维数组存储地图状态,下标从 1 开始(与输入的 Xᵢ、Yᵢ 坐标直接对应),A+5和B+5是冗余设计,避免因边界计算失误导致数组越界;left, right, top, bottom:分别表示每个泡泡水平/垂直方向的覆盖边界,通过max(..., 1)和min(..., 地图边界)确保不超出地图范围。
核心逻辑详解
- 地图初始化:双重循环将所有格子设为
1,表示初始状态下所有格子都是安全的; - 泡泡覆盖标记:
- 水平方向:遍历泡泡所在行的左右覆盖范围,将这些格子标记为
0(被覆盖); - 垂直方向:遍历泡泡所在列的上下覆盖范围,将这些格子标记为
0(被覆盖);
这里无需担心重复标记(比如同一格子被多个泡泡覆盖),因为标记为0后,后续重复标记不会改变其值;
- 水平方向:遍历泡泡所在行的左右覆盖范围,将这些格子标记为
- 安全格子统计:再次遍历地图,计数所有值为
1的格子,即为最终答案。
复杂度分析
- 时间复杂度:O(A×B + N×L_avg),其中 L_avg 是每个泡泡的平均扩散长度。
- 初始化地图和统计安全格子的时间均为 O(A×B),而 A×B≤40000,这部分操作极快;
- 处理 N 个泡泡时,每个泡泡的覆盖范围遍历次数最多为 2×L(水平+垂直),但由于 L 可能很大,但实际遍历范围被地图边界限制(最大为 A 或 B),且 N≤1000,总体操作次数可控,完全不会超时;
- 空间复杂度:O(A×B),二维数组存储地图状态,因 A×B≤40000,空间占用极小(4 万 int 类型仅约 160KB)。
测试用例验证
样例输入 1 验证
- 地图为 6×6=36 个格子;
- 3 个泡泡的十字覆盖范围叠加后,被覆盖的格子数为 24,剩余安全格子数 36-24=12,与样例输出一致;
样例输入 2 验证
- 地图为 4×5=20 个格子;
- 2 个泡泡的覆盖范围叠加后,被覆盖的格子数为 8,剩余安全格子数 20-8=12,与样例输出一致。
注意事项
- 坐标一致性:输入的 Xᵢ 是行数(对应数组第一维),Yᵢ 是列数(对应数组第二维),不要混淆;
- 边界处理:必须用
max和min限制覆盖范围,否则会出现数组下标越界错误(比如 Y-L+1 可能小于 1,X+L-1 可能大于 A); - 数组大小限制:虽然 A 和 B 最大为 20000,但 A×B≤40000,因此二维数组不会超出内存限制(若忽略该限制,直接定义
map[20005][20005]会导致内存溢出,这是题目给出的关键优化点)。
