【基础算法】多源 BFS

文章目录

  • 上文链接
  • [一、多源 BFS](#一、多源 BFS)
    • [1. 单源最短路 vs 多源最短路](#1. 单源最短路 vs 多源最短路)
    • [2. 解决方案](#2. 解决方案)
  • [二、OJ 练习](#二、OJ 练习)
    • [1. 矩阵距离 ⭐⭐](#1. 矩阵距离 ⭐⭐)
      • [(1) 解题思路](#(1) 解题思路)
      • [(2) 代码实现](#(2) 代码实现)
    • [2. 刺杀大使 ⭐⭐⭐](#2. 刺杀大使 ⭐⭐⭐)
      • [(1) 解题思路](#(1) 解题思路)
      • [(2) 代码实现](#(2) 代码实现)

上文链接

一、多源 BFS

1. 单源最短路 vs 多源最短路

  • 当问题中只存在一个起点时,这时的最短路问题就是单源最短路问题。

  • 当问题中存在多个起点而不是单一起点时,这时的最短路问题就是多源最短路问题。


2. 解决方案

多源 BFS 多源最短路问题的边权都为 1 时,此时就可以用多源 BFS 来解决。

具体来说,多源 BFS:

  • 初始化的时候,把所有的源点都加入到队列里面;

  • 然后正常执行 BFS 的逻辑即可。也就是初始化的时候,比普通的 BFS 多加入几个起点。


二、OJ 练习

1. 矩阵距离 ⭐⭐

【题目链接】

矩阵距离

(1) 解题思路

如果针对每一个 0,我们都直接去找最近的 1,我们需要对所有的 0 都来一次 BFS,这个时间复杂度是接受不了的。

但是我们如果反着来想,我们从每一个 1 开始向外扩展(即初始时把每一个 1 都加入到队列中),每遍历到一个 0 就更新一下最短距离。这样仅需一次 BFS,就可以把所有点距离的最短距离更新出来。这种思想就是正难则反


(2) 代码实现

cpp 复制代码
#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];
            // 多源 BFS
            if(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) 代码实现

cpp 复制代码
#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;
}
相关推荐
冷崖4 小时前
定时器的学习(二)
linux·c++·学习
B站计算机毕业设计之家4 小时前
深度学习实战:python动物识别分类检测系统 计算机视觉 Django框架 CNN算法 深度学习 卷积神经网络 TensorFlow 毕业设计(建议收藏)✅
python·深度学习·算法·计算机视觉·分类·毕业设计·动物识别
大胆飞猪4 小时前
高并发内存池日志
c++·项目
And_Ii4 小时前
LeetCode 3350. 检测相邻递增子数组 II
数据结构·算法·leetcode
想唱rap4 小时前
C++ string类的使用
开发语言·c++·笔记·算法·新浪微博
JAVA学习通4 小时前
Replication(下):事务,一致性与共识
大数据·分布式·算法
胖咕噜的稞达鸭4 小时前
C++中的父继子承(2)多继承菱形继承问题,多继承指针偏移,继承组合分析+高质量习题扫尾继承多态
c语言·开发语言·数据结构·c++·算法·链表·c#
蓝色汪洋4 小时前
Completed String easy
算法
铭哥的编程日记4 小时前
贪心算法精选30道编程题 (附有图解和源码)
算法·贪心算法