【基础算法】多源 BFS
文章目录
- 上文链接
- 一、多源 BFS
- 1. 单源最短路 vs 多源最短路
- 2. 解决方案
- 二、OJ 练习
- 1. 矩阵距离 ⭐⭐
- (1) 解题思路
- (2) 代码实现
- 2. 刺杀大使 ⭐⭐⭐
- (1) 解题思路
- (2) 代码实现
上文链接
- BFS
一、多源 BFS
1. 单源最短路 vs 多源最短路
-
当问题中只存在一个起点时,这时的最短路问题就是单源最短路问题。
-
当问题中存在多个起点而不是单一起点时,这时的最短路问题就是多源最短路问题。
2. 解决方案
多源 BFS 多源最短路问题的边权都为 1 时,此时就可以用多源 BFS 来解决。
具体来说,多源 BFS:
初始化的时候,把所有的源点都加入到队列里面;
然后正常执行 BFS 的逻辑即可。也就是初始化的时候,比普通的 BFS 多加入几个起点。
二、OJ 练习
1. 矩阵距离 ⭐⭐
【题目链接】
矩阵距离
(1) 解题思路
如果针对每一个 0,我们都直接去找最近的 1,我们需要对所有的 0 都来一次 BFS,这个时间复杂度是接受不了的。
但是我们如果反着来想,我们从每一个 1 开始向外扩展(即初始时把每一个 1 都加入到队列中),每遍历到一个 0 就更新一下最短距离。这样仅需一次 BFS,就可以把所有点距离的最短距离更新出来。这种思想就是正难则反。
(2) 代码实现
#include<iostream>
#include<queue>using namespace std;typedef pair<int, int> PII;
queue<PII> q;
const int N = 1010;
char st[N][N]; // 读入的矩阵
int dist[N][N]; // 输出的矩阵,标记每一个位置的距离
int n, m;int dx[] = {0, 0, -1, 1};
int dy[] = {1, -1, 0, 0};void bfs()
{int step = 0;while(!q.empty()){int sz = q.size();step++;while(sz--){int r = q.front().first;int c = q.front().second;q.pop();for(int i = 0; i < 4; i++){int rr = r + dx[i];int cc = c + dy[i];if(rr > 0 && rr <= n && cc > 0 && cc <= m && st[rr][cc] == '0'){q.push({rr, cc});st[rr][cc] = '1';dist[rr][cc] = step;}}}}
}int main()
{cin >> n >> m;for(int i = 1; i <= n; i++){for(int j = 1; j <= m; j++){cin >> st[i][j];// 多源 BFSif(st[i][j] == '1') q.push({i, j}); // 把所有 1 位置加入到队列}}bfs();for(int i = 1; i <= n; i++){for(int j = 1; j <= m; j++){cout << dist[i][j] << " ";}cout << endl;}return 0;
}
2. 刺杀大使 ⭐⭐⭐
【题目链接】
P1902 刺杀大使 - 洛谷
【题目描述】
某组织正在策划一起对某大使的刺杀行动。他们来到了使馆,准备完成此次刺杀,要进入使馆首先必须通过使馆前的防御迷阵。
迷阵由 n × m n\times m n×m 个相同的小房间组成,每个房间与相邻四个房间之间有门可通行。在第 n n n 行的 m m m 个房间里有 m m m 个机关,这些机关必须全部打开才可以进入大使馆。而第 1 1 1 行的 m m m 个房间有 m m m 扇向外打开的门,是迷阵的入口。除了第 1 1 1 行和第 n n n 行的房间外,每个房间都被使馆的安保人员安装了激光杀伤装置,将会对进入房间的人造成一定的伤害。第 i i i 行第 j j j 列 造成的伤害值为 p i , j p_{i,j} pi,j(第 1 1 1 行和第 n n n 行的 p p p 值全部为 0 0 0)。
现在某组织打算以最小伤害代价进入迷阵,打开全部机关,显然,他们可以选 择任意多的人从任意的门进入,但必须到达第 n n n 行的每个房间。一个士兵受到的伤害值为他到达某个机关的路径上所有房间的伤害值中的最大值,整个部队受到的伤害值为所有士兵的伤害值中的最大值。现在,这个恐怖组织掌握了迷阵的情况,他们需要提前知道怎么安排士兵的行进路线可以使得整个部队的伤害值最小。
【输入格式】
第一行有两个整数 n , m n,m n,m,表示迷阵的大小。
接下来 n n n 行,每行 m m m 个数,第 i i i 行第 j j j 列的数表示 p i , j p_{i,j} pi,j。
【输出格式】
输出一个数,表示最小伤害代价。
【示例一】
输入
4 2 0 0 3 5 2 4 0 0
输出
3
【说明/提示】
- 50 % 50\% 50% 的数据, n , m ≤ 100 n,m \leq 100 n,m≤100;
- 100 % 100\% 100% 的数据, n , m ≤ 1000 n,m \leq 1000 n,m≤1000, p i , j ≤ 1000 p_{i,j} \leq 1000 pi,j≤1000。
(1) 解题思路
其实这道题问的就是,在所有从第一行到最后一行的路径中,都有一个经过的数字的最大值,求这些最大值中最小的那个是多少。如果我们把所有的路径都枚举一遍,显然是会超时的。但是仔细一看这道题说的是最大值最小的问题,我们自然就要想到二分。
对于我们枚举一个数值 x
,检查在伤害不超过 x
的情况下能够打开所有机关(能否从最上面走到最下面)。如果不能,那么这个 x
一定枚举大了,反之则小了。而这里的枚举 x
显然是用二分去枚举,检查能否打开所有机关则可用多源 BFS。
(2) 代码实现
#include<iostream>
#include<queue>
#include<cstring>using namespace std;typedef pair<int, int> PII;
const int N = 1010;
int mat[N][N];
bool vis[N][N];
int n, m;int dx[] = {0, 0, -1, 1};
int dy[] = {1, -1, 0, 0};// 在伤害不超过 x 的情况下打开全部机关
bool check(int x)
{memset(vis, false, sizeof(vis));queue<PII> q;// 把第一行所有位置都加入到队列中for(int j = 1; j <= m; j++){q.push({1, j});vis[1][j] = true;}// 多源 BFS// 看 BFS 能不能走到最后一行,这是检验能否到达的标准while(!q.empty()){int r = q.front().first;int c = q.front().second;q.pop();for(int i = 0; i < 4; i++){int rr = r + dx[i];int cc = c + dy[i];if(rr == n) return true; // 如果能走到最后一行,则返回 true// 只把合法并且值小于 x 的位置加入到队列中if(rr > 0 && rr < n && cc > 0 && cc <= m && !vis[rr][cc] && mat[rr][cc] <= x){q.push({rr, cc});vis[rr][cc] = true;}}}return false;
}int main()
{cin >> n >> m;for(int i = 1; i <= n; i++)for(int j = 1; j <= m; j++)cin >> mat[i][j];int left = 0, right = 1000;while(left < right){int mid = (left + right) / 2;if(check(mid)) right = mid;else left = mid + 1;}cout << left << endl;return 0;
}