血色先锋队

GitHub跳转原题关系图返回列表

把所有感染源同时作为 BFS 起点,一次多源 BFS 预处理整张图,再直接回答每个领主的感染时间。

OJ: luogu

题目 ID: P1332

难度:普及-

标签:bfs最短路图论网格多源bfs

日期: 2026-06-19 08:26

题意

有一个 n × m 的方阵,里面的人会感染瘟疫。

一开始给出 a 个感染源,它们在第 0 小时就已经被感染。

之后每过 1 小时,瘟疫会向上、下、左、右扩散一格。

现在还给出 b 个领主的位置,要求输出每个领主在第几小时会被感染。

样例里两个感染源分别在 (1,1)(5,4),所以整张图的感染时间,其实就是每个格子到这两个源点的四联通最短距离的较小值。

思路

最直观的办法,是把感染过程一小时一小时模拟:

  • 0 小时先有一批感染源;
  • 1 小时这些点向外扩一层;
  • 2 小时再继续扩。

这个版本很容易理解:

cpp
#include <bits/stdc++.h>
using namespace std;

const int MAXN = 35;

int n, m, a, b;
int dista[MAXN][MAXN];
bool infected[MAXN][MAXN];
int dx[4] = {-1, 1, 0, 0};
int dy[4] = {0, 0, -1, 1};

struct Node {
    int x;
    int y;
};

bool in_board(int x, int y) {
    return x >= 1 && x <= n && y >= 1 && y <= m;
}

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

    cin >> n >> m >> a >> b;

    vector<Node> cur;
    memset(dista, -1, sizeof(dista));

    for (int i = 1; i <= a; i++) {
        int x, y;
        cin >> x >> y;
        if (!infected[x][y]) {
            infected[x][y] = true;
            dista[x][y] = 0;
            cur.push_back((Node){x, y});
        }
    }

    // 朴素模拟:按“第 0 小时、第 1 小时……”一层一层扩散瘟疫。
    int hour = 0;
    while (!cur.empty()) {
        vector<Node> nxt;
        for (int idx = 0; idx < (int) cur.size(); idx++) {
            int x = cur[idx].x;
            int y = cur[idx].y;

            for (int i = 0; i < 4; i++) {
                int nx = x + dx[i];
                int ny = y + dy[i];

                if (!in_board(nx, ny)) {
                    continue;
                }
                if (infected[nx][ny]) {
                    continue;
                }

                infected[nx][ny] = true;
                dista[nx][ny] = hour + 1;
                nxt.push_back((Node){nx, ny});
            }
        }
        cur.swap(nxt);
        hour++;
    }

    for (int i = 1; i <= b; i++) {
        int x, y;
        cin >> x >> y;
        cout << dista[x][y] << '\n';
    }

    return 0;
}

但如果对每个领主单独去搜最近感染源,会有大量重复工作。

同时扩散 = 多源 BFS

题目里所有感染源是“同时开始扩散”的。

这正好对应图论里的多源 BFS:

  • 把所有感染源一起作为第 0 层;
  • 同时入队;
  • 像普通 BFS 一样往外一层层扩展。

谁先到达某个格子,谁就决定了这个格子的最早感染时间。

为什么这样是对的

因为每次传播到相邻格子的代价都相同,都是 1 小时。

所以这本质上就是无权图最短路问题。

在无权图里,BFS 第一次访问到某个点时,得到的距离一定是最短的。

这里的距离,正好就是感染时间。

正式做法

  1. 建一个 dista[x][y],表示格子 (x,y) 最早在第几小时感染;
  2. 所有感染源的距离初始化为 0,并全部入队;
  3. 做一次多源 BFS,把整张图的感染时间全部求出来;
  4. 之后每个领主查询,直接输出它所在格子的距离。

代码

cpp
#include <bits/stdc++.h>
using namespace std;

const int MAXN = 505;

int n, m, a, b;
int dista[MAXN][MAXN];
int dx[4] = {-1, 1, 0, 0};
int dy[4] = {0, 0, -1, 1};

struct Node {
    int x;
    int y;
};

bool in_board(int x, int y) {
    return x >= 1 && x <= n && y >= 1 && y <= m;
}

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

    cin >> n >> m >> a >> b;

    memset(dista, -1, sizeof(dista));
    queue<Node> q;

    for (int i = 1; i <= a; i++) {
        int x, y;
        cin >> x >> y;
        if (dista[x][y] == -1) {
            dista[x][y] = 0;
            q.push((Node){x, y});
        }
    }

    while (!q.empty()) {
        Node u = q.front();
        q.pop();

        for (int i = 0; i < 4; i++) {
            int nx = u.x + dx[i];
            int ny = u.y + dy[i];

            if (!in_board(nx, ny)) {
                continue;
            }
            if (dista[nx][ny] != -1) {
                continue;
            }

            dista[nx][ny] = dista[u.x][u.y] + 1;
            q.push((Node){nx, ny});
        }
    }

    for (int i = 1; i <= b; i++) {
        int x, y;
        cin >> x >> y;
        cout << dista[x][y] << '\n';
    }

    return 0;
}

复杂度

  • 时间复杂度:O(nm)O(nm)
  • 空间复杂度:O(nm)O(nm)

总结

这题的关键不在搜索本身,而在于识别出“多个源点同时扩散”这个结构。

一旦看到这点,就应该直接想到多源 BFS:一次预处理整张图,然后所有查询都只要查表。

一图流解析

这张图把本题的建模、关键转移、实现检查和训练方法压缩到一页,适合读完正文后复盘。

一图流解析