📘题目描述:开垦荒地 传送门:https://www.luogu.com.cn/problem/B4263
小杨有一大片荒地,可以表示为一个 n n n 行 m m m 列的网格图。
小杨想要开垦这块荒地,但荒地中一些位置存在杂物,对于一块不存在杂物的荒地,该荒地可以开垦当且仅当其上下左右四个方向相邻的格子均不存在杂物。
小杨可以选择至多一个位置 ,清除该位置的杂物,移除后该位置变为荒地。小杨想知道,在清除至多一个位置的杂物的情况下,最多能够开垦多少块荒地。
✅输入格式:
第一行包含两个正整数 n , m n, m n,m,含义如上所述。
之后 n n n 行,每行一个长度为 m m m 的字符串,仅包含 .
和 #
:
.
表示荒地#
表示杂物
✅输出格式:
输出一个整数,代表在清除至多一个杂物后,最多可以开垦的荒地块数。
🧪输入样例 #1
3 5
.....
.#..#
.....
✅输出样例 #1
11
📌样例说明:
原图:
.....
.#..#
.....
清除中间那块杂物 mat[2][2]
后变为:
.....
....#
.....
每一行均有多块可开垦地,最多为:
- 第一行:前 4 块
- 第二行:前 3 块
- 第三行:前 4 块
总共:11块荒地
这道题目是一个典型的模拟搜索+贪心类网格题,其关键在于判断某个荒地是否可以被开垦(满足四个方向没有杂物),以及在至多清除一个杂物的前提下,最多能获得多少"可开垦"的荒地数。
🧩 一、题目简述(问题理解)
给定一个大小为 n x m
的网格图:
.
表示"荒地"#
表示"杂物"
定义"可开垦的荒地 ":一个 .
格子如果它的上下左右四个方向 都没有杂物,则该格子可以被开垦。
你可以选择清除最多一个 #
杂物 ,使得整体可以开垦的荒地数最大。
🧮 二、解题思路分析
✅ Step 1:初始统计
-
首先遍历每个格子:
- 如果当前格子是
.
并且它的四个相邻格子中恰好有一个#
,那么去掉那个#
就能使当前格子变得可开垦。 - 如果本身四周无杂物,那它就已经是可开垦的。
- 如果当前格子是
用 a[i][j]
数组来辅助统计:表示如果清除了某个杂物,它能帮助多少个周围的荒地变得可开垦。
✅ Step 2:模拟清除一个杂物
- 我们统计:对每一个杂物点
#
,它若被清除,会让周围多少个点从"不可开垦"变成"可开垦"。 - 记录在
a[i][j]
数组中。
✅ Step 3:寻找最优解
- 原始能开垦的荒地数为
ans
。 - 再从
a[i][j]
中找出最大的贡献值mx
。 - 答案即为:
ans + mx
🧠 三、关键变量说明
变量 | 含义 |
---|---|
mat[i][j] |
原始地图字符矩阵 |
a[i][j] |
杂物被清除后可能产生的额外可开垦荒地数量 |
ans |
原本可开垦的荒地数(不移除杂物) |
mx |
清除某个杂物后,额外可以开垦的最大值 |
d[4][2] |
表示方向向量,上下左右 |
💻C++ 题解代码(含注释)
cpp
#include<bits/stdc++.h>
using namespace std;
const int N = 1005;
char mat[N][N]; // 地图
int a[N][N]; // 杂物清除后带来的增益
const int d[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}}; // 上下左右
int main() {
int n, m, ans = 0;
scanf("%d%d", &n, &m);
assert(1 <= n && n <= 1000);
assert(1 <= m && m <= 1000);
for (int i = 1; i <= n; i++)
scanf("%s", mat[i] + 1); // 输入地图,从下标1开始
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
int num = 0, p = -1;
// 统计周围方向上有多少个 #
for (int k = 0; k < 4; k++) {
int ni = i + d[k][0];
int nj = j + d[k][1];
if (mat[ni][nj] == '#') {
num++;
p = k;
}
}
if (mat[i][j] == '.' && num == 1) {
// 这个荒地被一个杂物阻碍,记录这个杂物带来的贡献
int pi = i + d[p][0];
int pj = j + d[p][1];
a[pi][pj]++;
}
else if (mat[i][j] == '.' && num == 0) {
// 本身就可以开垦
ans++;
}
else if (mat[i][j] == '#' && num == 0) {
// 杂物点自身四周没杂物,也可能是一个潜在的清除点
a[i][j]++;
}
}
}
// 找出最多能额外开垦的数量
int mx = 0;
for (int i = 1; i <= n; i++)
for (int j = 0; j <= m; j++)
mx = max(mx, a[i][j]);
cout << ans + mx << endl;
return 0;
}
🔍 四、C++ 代码逐行讲解
cpp
const int N = 1005; // 最大网格尺寸
char mat[N][N]; // 地图输入
int a[N][N]; // 杂物点的贡献值记录
const int d[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}}; // 4个方向
cpp
int main() {
int n, m, ans = 0;
scanf("%d%d", &n, &m); // 输入行列
...
cpp
for (int i = 1; i <= n; i++)
scanf("%s", mat[i] + 1); // 注意下标从1开始,方便处理边界
✅ 统计原始的开垦数与杂物的贡献
cpp
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++) {
int num = 0, p = -1;
for (int k = 0; k < 4; k++) {
if (mat[i + d[k][0]][j + d[k][1]] == '#') {
num++; // 四周杂物数量
p = k; // 记录杂物位置
}
}
// 如果正中是 '.' 且四周仅有1个杂物,则将那个杂物点的贡献 +1
if (mat[i][j] == '.' && num == 1)
a[i + d[p][0]][j + d[p][1]]++;
// 当前点是 '.' 且周围没有杂物,直接可开垦
else if (mat[i][j] == '.' && num == 0)
ans++;
// 当前是 '#' 且不影响任何点,也可以作为未来考虑清除的点
else if (mat[i][j] == '#' && num == 0)
a[i][j]++;
}
✅ 找最大贡献值
cpp
int mx = 0;
for (int i = 1; i <= n; i++)
for (int j = 0; j <= m; j++)
mx = max(mx, a[i][j]);
✅ 输出最终答案
cpp
cout << ans + mx << endl;
🧪 五、样例解释
输入:
3 5
.....
.#..#
.....
初始没有清除杂物时,能开垦:
- 第一行 4 个格子(除了右边一个有相邻的
#
) - 第二行 2 个格子
- 第三行 4 个格子
共:10个
清除 (2,2) 的 #
后,第二行从 .#..#
变为 ....#
则第二行中间的点也变得可开垦,提升了 1 个
最终为 11
🧾 六、总结
- 本题考查对网格搜索、状态统计、贪心选择的理解。
- 注意细节如:不能重复计算,清除杂物仅限一次。
- 辅助数组
a[i][j]
设计巧妙,避免重复模拟,效率较高。