#LC1254. 封闭岛屿的数量

封闭岛屿的数量

你好,同学!在掌握了基础的“岛屿数量”统计后,我们今天进入进阶课题:“封闭岛屿的数量”

这道题在 NOI 竞赛中属于典型的**“带边界条件的连通块搜索”**。虽然核心依然是 DFS/BFS,但它引入了一个关键逻辑:如何定义并判断一个连通块是否“触碰了边界”。


一、 题目描述:封闭岛屿的数量 (Number of Closed Islands)

题目背景: 在资源勘探中,不仅要统计区域,还要区分哪些区域是完全被包围在内部的“核心区”。

任务描述: 给你一个大小为 M×NM \times N 的二维网格 grid。其中:

  • 0 表示陆地。
  • 1 表示水域。 注意:本题中 0 是陆地,这与基础题不同。 封闭岛屿 是指完全由水域包围的岛屿(上、下、左、右)。如果一个岛屿触碰到了网格的边界(即第一行、最后一行、第一列、最后一列),那么它就不是封闭的,因为它有一边被网格边缘挡住,而不是被水包围。

请计算网格中“封闭岛屿”的总数。

输入格式: 第一行包含两个整数 MMNN1M,N1001 \le M, N \le 100。 接下来的 MM 行,每行包含 NN 个整数(01),用空格分隔。

输出格式: 输出一个整数,表示封闭岛屿的数量。

样例 1:

输入:

5 8
1 1 1 1 1 1 1 0
1 0 0 0 0 1 1 0
1 0 1 0 1 1 1 0
1 0 0 0 0 1 0 1
1 1 1 1 1 1 1 0

输出:

2

样例数据说明: 虽然网格里有好几个由 0 组成的连通块,但只有中间两个完全被 1 包围。最右侧那一列的 0 触碰了边界,不计入封闭岛屿。


二、 预备知识点

  1. Flood Fill (种子填充): 从一个点开始扩散。
  2. 边界处理 (Boundary Condition): 识别处于 (0,y),(M1,y),(x,0),(x,N1)(0, y), (M-1, y), (x, 0), (x, N-1) 的节点。
  3. 逆向思维/预处理: 与其在搜索过程中判断是否封闭,不如提前排除“非封闭”的干扰项。

三、 启发式引导:草稿纸上的排除法

当题目要求我们找“不接触边界”的连通块时,在 NOI 竞赛中我们通常有两种策略:

策略 A:排除法(教练推荐:思路最清晰)

  1. 第一步: 扫描网格的四条边
  2. 第二步: 如果在边缘发现了陆地 0,说明它所属的整个岛屿都不是封闭的。
  3. 第三步: 立即以这些边缘 0 为起点,进行一次 DFS,把与之相连的所有陆地统统变成水域 1
  4. 第四步: 处理完所有边缘岛屿后,剩下的 0 连通块必然是封闭的。现在只需要按部就班统计“岛屿数量”即可。

策略 B:搜索标志位

在 DFS 遍历一个连通块时,设置一个全局变量 isClosed = true。如果遍历过程中遇到了 x == 0x == M-1 等情况,将 isClosed 改为 false

复杂度分析:

  • 时间复杂度: O(M×N)O(M \times N)。无论哪种策略,每个格子最多被访问两次。
  • 空间复杂度: O(M×N)O(M \times N)。递归栈深度。

四、 算法流程图 (Mermaid 风格)

我们采用策略 A(预处理排除法)进行逻辑绘制:

graph TD
    Start("开始") --> Step1("第一步: 排除边缘岛屿")
    Step1 --> LoopEdge("遍历四条边界上的每个格子")
    LoopEdge --> Check0{"当前格子是 0 吗?"}
    Check1 -- "是" --> Fill1("以该点 DFS 将连通块全部填充为 1")
    Check0 -- "是" --> Fill1
    Check0 -- "否" --> NextEdge("检查下一个边缘点")
    Fill1 --> NextEdge
    
    NextEdge --> AllEdgeDone{"边缘遍历完了吗?"}
    AllEdgeDone -- "是" --> Step2("第二步: 统计内部岛屿")
    
    Step2 --> ScanAll("遍历网格内部所有点 i, j")
    ScanAll --> Found0{"发现 grid_i_j == 0?"}
    Found0 -- "是" --> AnsAdd("封闭岛屿计数 ans 加 1")
    AnsAdd --> DFS2("以该点 DFS 将连通块填充为 1")
    DFS2 --> NextPoint("继续扫描下一个点")
    Found0 -- "否" --> NextPoint
    
    NextPoint --> AllDone{"全网格扫描完了吗?"}
    AllDone -- "是" --> Output("输出 ans")

五、 读题理解关键词

在 NOI 考试中,理解此类题意的关键词在于:

  1. “封闭” (Closed): 意味着只要有一个点出界,整个集合就失效。这种“一票否决制”往往预示着可以先处理掉所有“不合规”的集合。
  2. “0 和 1 的含义”: 陆地和水域的数字定义经常反转,读题时一定要在草稿纸上圈出来,避免写反。
  3. “上下左右”: 明确是四连通还是八连通。
  4. 边界范围: 100×100100 \times 100 的数据范围非常小,DFS 完全不会爆栈,甚至可以使用最朴素的算法。

教练笔记: 策略 A 的美妙之处在于它把一个复杂的问题(边搜边判断)拆解成了两个简单的问题(先删边界,再数中间)。这种“简化问题模型”的能力,是冲击金牌的重要素质。加油!