B4530 网格搜索 Flood Fill

快递无人机

这一题不要算圆心半径。把地图四周当成“外面”,让水从外面流进去,`*` 是墙,最后没被水染到的 `o` 才在圆内部。

一、先回答问题

直接结论

统计内部包裹 = 统计没有被外部染到的 `o`

从地图最外圈所有非 `*` 格子开始 BFS。水只能走上下左右,遇到 `*` 停下。搜索结束后,仍然没有访问到的包裹点 `o` 就被边界圈住了。

先避开误区

不要做几何题

题目给的是字符图,不要求求圆心、半径或距离。`*` 已经把边界画出来,我们只需要判断一个格子和“外部”是否连通。

二、把圆换成连通性问题

核心模型
地图四周
一定属于外部入口
BFS 染所有能到达的
非 `*` 格子
`*` 是墙
水不能穿过边界
没被染到的 `o`
就在内部

三、互动演示:从外面灌水

`*` 墙 已染外部 队列前沿 内部包裹

四、自己选答案

1. BFS 的起点应该放在哪里?

2. 搜索结束后,哪个包裹算正确投送?

3. 为什么 `*` 不入队?

五、手推过程

阶段 课堂动作 得到的信息
1. 放入口 把四条边上所有不是 `*` 的格子放进队列。 这些格子确定属于圆外。
2. 扩张 每次取出队头,向上下左右尝试走一步。 只要不是越界、不是 `*`、没访问过,就继续染成外部。
3. 统计 扫一遍整张图,数 `a[i][j] == 'o' && !vis[i][j]`。 这些包裹没有和外部连通,因此在内部。

六、C++14 参考代码

外部 Flood Fill:把墙外全部染掉
#include <bits/stdc++.h>
using namespace std;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n, m;
    cin >> n >> m;
    vector<string> a(n);
    for (int i = 0; i < n; i++) cin >> a[i];

    vector<vector<int>> vis(n, vector<int>(m, 0));
    queue<pair<int, int>> q;

    auto add = [&](int x, int y) {
        if (x < 0 || x >= n || y < 0 || y >= m) return;
        if (vis[x][y] || a[x][y] == '*') return;
        vis[x][y] = 1;
        q.push({x, y});
    };

    for (int i = 0; i < n; i++) {
        add(i, 0);
        add(i, m - 1);
    }
    for (int j = 0; j < m; j++) {
        add(0, j);
        add(n - 1, j);
    }

    int dx[4] = {1, -1, 0, 0};
    int dy[4] = {0, 0, 1, -1};
    while (!q.empty()) {
        auto [x, y] = q.front();
        q.pop();
        for (int k = 0; k < 4; k++) {
            add(x + dx[k], y + dy[k]);
        }
    }

    int ans = 0;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            if (a[i][j] == 'o' && !vis[i][j]) ans++;
        }
    }
    cout << ans << '\n';
    return 0;
}
`add` 同时负责判越界、判墙、判重,主循环就很干净。
四条边全部作为起点,避免只从左上角出发漏掉外部隔开的区域。
每个格子最多入队一次,时间复杂度 `O(nm)`,空间复杂度 `O(nm)`。