跳到主要内容

BISHI82. 没挡住洪水

题目描述​

吴铭市洪水肆虐,市政部门临时砌起的围墙是豆腐渣工程,被洪水冲毁, 只留下用 # 表示的空地。地图是一个 N×N 的字符矩阵:. 表示已经被洪水 淹没的区域,# 表示空地。四联通的 # 组成一个区域。

洪水还会继续上涨:所有与「已经被洪水淹没的区域」上下左右相邻的空地格子, 都会在这一次上涨中被淹没。请计算此次上涨后,有多少个空地区域会被 完全淹没。

输入格式​

第一行输入整数 N。 随后 N 行,每行 N 个字符,构成地图,只含 . 与 #。 已知边界四条边全部是已经被洪水淹没的区域。

输出格式​

输出一个整数,表示一天后会完全消失的空地区域数量。

示例​

示例 1​

输入:

1
.

输出:

0

说明: 地图全是水,没有任何空地区域,答案为 0。


解题思路​

第一步:理解问题本质​

「一个区域被完全淹没」当且仅当:它的每一个格子都与洪水相邻。

原因在于洪水这次只涨一层 —— 只有与「已淹没区域」上下左右相邻的空地 才会进水。如果区域内存在一个格子,它的上下左右全是 #(也就是被同区域 的格子包围),那它这次就不会进水,区域自然没有被完全淹没。

第二步:暴力做法​

枚举每个 #,判断它这次是否会被淹没;然后枚举所有四联通区域, 看区域里是否所有格子都会被淹没。逻辑上正确,但要反复扫描地图, 实现容易写得混乱。

第三步:BFS 找连通块 + 逐格判断(推荐)​

一次遍历即可完成:

  1. 对每个 尚未访问 的 #,启动一次 BFS/DFS,收集整个四联通区域;
  2. 在扩散的同时,检查每个格子的四个方向: 只要有任意一个方向是 .(或越界),就说明这个格子临水、这次会被淹没;
  3. 给区域打一个标记 every_cell_touches_water: 只要遇到一个格子四邻全是 #(不临水),就把它置为 False;
  4. 遍历完该区域后,若标记仍为 True,答案 +1。

「找连通块」和「判断是否临水」可以在同一次 BFS 里做完, 不需要开两遍地图。


完整代码实现​

算法可视化 · 逐帧回放这道题的代码10 帧 · 录制输入
import sys
from collections import deque


def solve() -> None:
"""统计每个 '#' 区域是否每个格子都临水,输出完全淹没的区域数"""
data = sys.stdin.read().split()
if not data:
return
n = int(data[0])
grid = data[1:1 + n]

seen = [[False] * n for _ in range(n)]
dirs = ((1, 0), (-1, 0), (0, 1), (0, -1))
fully_submerged = 0

for i in range(n):
for j in range(n):
if grid[i][j] != '#' or seen[i][j]:
continue
# 找到一个新区域,BFS 收集它所有格子
seen[i][j] = True
queue = deque([(i, j)])
every_cell_touches_water = True
while queue:
x, y = queue.popleft()
touches_water = False
for dx, dy in dirs:
nx, ny = x + dx, y + dy
if not (0 <= nx < n and 0 <= ny < n):
touches_water = True # 出界也算临水
elif grid[nx][ny] == '.':
touches_water = True
elif not seen[nx][ny]:
seen[nx][ny] = True
queue.append((nx, ny))
if not touches_water:
every_cell_touches_water = False
if every_cell_touches_water:
fully_submerged += 1

sys.stdout.write(str(fully_submerged) + "\n")


if __name__ == "__main__":
solve()

示例推演​

以一个 5×5 的迷宫为例:

. . . . .
. # . # .
. . . . .
. . . . .
. . . . .

两个孤立的 # 各自成区域。坐标 (2,2) 的 # 四个方向都是 ., 临水 → 被淹没;坐标 (2,4) 的 # 同样临水 → 被淹没。 两个区域都被完全淹没,答案是 2。

再换成一个 3×3 的实心块:

. . . . .
. # # # .
. # # # .
. # # # .
. . . . .

四个角上的 # 临水,但中心格 (3,3) 的上下左右全是 #,不临水, 所以这个区域没有被完全淹没,答案为 0 —— 这正是本题的关键: 「有格子被围在里面」就说明洪水这次淹不透它。


复杂度分析​

解法时间复杂度空间复杂度说明
反复扫描地图O(N^4) 量级O(N^2)不推荐
BFS 连通块 + 逐格判断O(N^2)O(N^2)推荐,每个格子只访问一次

易错点总结​

1. 把「区域临水」当成「区域被完全淹没」​

区域只要挨着洪水就会被部分淹没;要「完全」淹没, 必须每一个格子都临水。少判断一个格子就会多算。

2. 越界方向的处理​

题目保证边界四条边是水,所以 # 不会出现在边界上。 但在代码里把越界也算作「临水」更安全,不会误判。

3. 标记数组的时机​

同一个区域可能被多次扫到,必须在入队时就标记 seen, 否则一个连通块会被重复 BFS,答案翻倍。


扩展思考​

  • 如果问的是「这次上涨后还剩多少个空地区域没被淹」, 只需统计 every_cell_touches_water 为 False 的区域数。
  • 如果洪水可以持续上涨(多层),那么任何与外界连通的区域最终都会被淹没, 问题就退化成「统计包围起来的区域」,是另一个经典题。

相关题目​

自测​

加载评论中...