惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

MyScale Blog
MyScale Blog
博客园 - 司徒正美
A
About on SuperTechFans
Vercel News
Vercel News
H
Hackread – Cybersecurity News, Data Breaches, AI and More
爱范儿
爱范儿
I
InfoQ
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
博客园_首页
Google DeepMind News
Google DeepMind News
T
Tailwind CSS Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
F
Fortinet All Blogs
S
SegmentFault 最新的问题
阮一峰的网络日志
阮一峰的网络日志
D
Docker
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
G
Google Developers Blog
Stack Overflow Blog
Stack Overflow Blog
M
MIT News - Artificial intelligence
Jina AI
Jina AI
H
Help Net Security
量子位
IT之家
IT之家

博客园 - RonChen

Sunday 算法 康托展开 抽屉原理 区间合并 距离和的最小值 归并排序与逆序对 快速排序与快速选择 同余分析 差分约束 Treap 点分治 莫队算法 分块 扫描线 错排问题 Sprague-Grundy (SG) 函数及其应用 容斥原理 卢卡斯定理 线性基 高斯消元 勒让德公式 次短路 分层图最短路 01 图最短路 洪水填充 双向搜索 迭代加深搜索 剪枝 最小表示法 表达式计算
多源 BFS
RonChen · 2026-08-02 · via 博客园 - RonChen

在标准的单源广度优先搜索中,从网格地图中的某一个确定起点出发,逐步按层向四周邻近格子扩张,用来求解网格图/棋盘中的最少移动步数(最短距离)问题

然而,在许多实际题目中,起点的数量往往不止一个。例如:

  • 多个火源同时蔓延,求整栋大楼被烧毁的时间。
  • 多个基地同时发射信号,求网格地图上各个区域最早接收到信号的时间。
  • 求网格图上每个 \(0\) 格子到离它最近\(1\) 格子的距离。

类似这类拥有多个起始位置,且关注每个位置到“最近”起始位置的步数/距离的问题,即为多源 BFS 问题。

为什么不能对每个起点分别做单源 BFS?

假设网格共有 \(R\)\(C\) 列,总格数为 \(N = R \times C\),其中起点共有 \(k\) 个。

如果对每个起点分别跑一次单源 BFS,每次单源 BFS 遍历网格的时间复杂度为 \(O(N)\),总时间复杂度为 \(O(k \cdot N)\)。当起点数量较多,比如 \(k \apporx N\) 时,时间复杂度会暴涨至 \(O(N^2) = O((R \times C)^2)\)。在 \(1000 \times 1000\) 的网格中,计算次数高达 \(10^{12}\)

假设网格中存在起点集合 \(S = \{s_1, s_2, \dots, s_k\}\),可以想象存在一个虚构的“超级起点” \(S_0\)

  1. 想象在第 \(0\) 步时,超级起点 \(S_0\) 可以不耗费任何步数(0 步)瞬间到达所有的真实起点 \(s_1, s_2, \dots, s_k\)
  2. 问题便等价转换为了:求从超级起点 \(S_0\) 出发,到达网格中任意格子的最少步数

image

因为从 \(S_0\) 到各个真实起点的步数均为 \(0\),在 BFS 初始化时:

  1. 先将 \(S_0\) 想象为第一层。
  2. 展开 \(S_0\) 后,第一轮扩展会将 \(s_1, s_2, \dots, s_k\) 全部放入队列,且起点步数统一定义为 \(0\)

在实际编写代码时,无需构造复杂的虚构逻辑,只需在算法启动前,将所有起点一次性全部放入 BFS 队列中,作为第 \(0\) 层(初始层)位置即可。

例题:P1332 血色先锋队

在一个 \(n \times m \ (1 \le n,m \le 500)\) 的网格矩阵中,分布着 \(a\) 个感染源和 \(b \ (1 \le a,b \le 10^5)\) 个领主。感染源初始感染时间为 \(0\),从第 \(0\) 小时开始,已被感染过的格子每过 \(1\) 小时会向上下左右相邻的 \(4\) 个格子扩散瘟疫。要求计算出给定的 \(b\) 个领主分别被感染的最早时间,并按输入的顺序输出。

参考代码
#include <iostream>
#include <queue>
using namespace std;
using pi = pair<int, int>;
const int N = 505;
// 定义上下左右四个方向的偏移量
const int X[] = {-1, 1, 0, 0};
const int Y[] = {0, 0, -1, 1};
int d[N][N]; // 记录每个坐标被感染的最早时间
int main()
{
    int n, m, a, b; 
    cin >> n >> m >> a >> b;
    // 初始化为 -1,表示尚未被感染
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            d[i][j] = -1;
        }
    }
    queue<pi> q;
    // 读入所有感染源并作为多源起点同时入队
    while (a--) {
        int x, y; cin >> x >> y;
        if (d[x][y] == -1) {
            d[x][y] = 0;
            q.push({x, y});
        }
    }
    // 多源广度优先搜索(BFS)
    while (!q.empty()) {
        pi p = q.front();
        q.pop();
        int x = p.first, y = p.second;
        for (int i = 0; i < 4; i++) {
            int nx = x + X[i], ny = y + Y[i];
            // 检查边界及未访问状态
            if (nx >= 1 && nx <= n && ny >= 1 && ny <= m && d[nx][ny] == -1) {
                d[nx][ny] = d[x][y] + 1; // 距离加 1
                q.push({nx, ny});
            }
        }
    }
    // 依次回答 b 个领主的查询
    while (b--) {
        int x, y; cin >> x >> y;
        cout << d[x][y] << "\n";
    }
    return 0;
}

网格图上共 \(n \times m\) 个节点,BFS 保证每个节点最多入队和出队一次,处理四周邻居节点耗时与总节点数成正比,即 \(O(n \times m)\)。多源起点入队耗时 \(O(a)\),最后的查询阶段回答 \(b\) 个领主的位置耗时 \(O(b)\),总时间复杂度为 \(O(n \times m + a + b)\)