第二章 搜索 No.2多源bfs,最小步数与双端队列广搜

news2024/9/22 3:54:57

文章目录

      • 多源bfs:173. 矩阵距离
      • 最小步数:1107. 魔板
      • 双端队列bfs:175. 电路维修

image.png

根据Dijkstra的正确性可以验证bfs的正确性


多源bfs:173. 矩阵距离

173. 矩阵距离 - AcWing题库
image.png

输出01矩阵中的所有点到1的最短曼哈顿距离,反向思考,求1到图中所有点的最短距离,由于图中可能有多个1,即多个源点。所以这是一题多源bfs问题
与图论中的多源最短路:求任意两点间的最短距离不同,多源bfs求的是多个源点到多个终点中的最近终点距离,即多源bfs的源点与终点所属集合是任意两点间这个集合的子集
考虑将多源问题转换成单源问题,建立虚拟源点,在虚拟源点与其他源点之间建立权值为0的边,那么从虚拟源点到终点的最短距离在数值上与多个源点到终点的最短距离相等

如何用代码实现?假设建立了虚拟源点,将虚拟源点入队,进行bfs。那么第一次扩展,虚拟源点会将其他所有源点入队,并更新它们的最短距离为0(因为源点为1,因此1到1的最短距离为0),然后再用这些源点向外扩展
所以多源最短路与单源最短路是相似的,单源最短路要先将一个源点入队并更新距离,而多源最短路则是将所有源点入队并更新距离

#include <iostream>
#include <cstring>
using namespace std;

typedef pair<int, int> PII;
const int N = 1010, M = N * N;
PII q[M];
char g[N][N];
int n, m, dis[N][N];

int dx[4] = { 0, 0, -1, 1 }, dy[4] = { 1, -1, 0, 0 };

void bfs()
{
    int tt = -1, hh = 0;
    for (int i = 0; i < n; ++ i )
        for (int j = 0; j < m; ++ j )
            if (g[i][j] == '1')
                q[ ++ tt ] = { i, j }, dis[i][j] = 0;
                
    while (tt >= hh)
    {
        auto t = q[hh ++ ];
        int tx = t.first, ty = t.second;
        for (int i = 0; i < 4; ++ i )
        {
            int nx = tx + dx[i], ny = ty + dy[i];
            if (nx < 0 || nx >= n || ny < 0 || ny >= m) continue;
            if (dis[nx][ny] != -1) continue;
            dis[nx][ny] = dis[tx][ty] + 1;
            q[ ++ tt ] = { nx, ny }; 
        }
    }
}

int main()
{
    memset(dis, -1, sizeof(dis));
    scanf("%d%d", &n, &m);
    for (int i = 0; i < n; ++ i )
        scanf("%s", g[i]);
    
    bfs();
    for (int i = 0; i < n; ++ i )
    {
        for (int j = 0; j < m; ++ j )
            printf("%d ", dis[i][j]);
        printf("\n");
    }
    return 0;
}

最小步数:1107. 魔板

1107. 魔板 - AcWing题库
image.png

一般都是用哈希存储,正常的bfs最短路中,用数组下标表示点的编号,数组内容表示最短距离
而最小步数模型中,将整张图看成一个点,那么数组下标肯定无法很好地表示一张图。可以使用unordered_map,将点的状态表示成first,与源点的最短距离表示成second
而最小步数的难点在于:如何表示每个点的状态?
这道题中,点的状态是一个二维矩阵的信息,一般情况都是将二维矩阵转换成一维,用string表示状态
所以这道题用unordered<string, int>表示最短距离,由于还需要输出递达最短距离的操作步骤,所以需要保存递达每一个点的前一个点,以及前一个点变换成当前点经过的操作
这里用unordered<string, pair<string, char>> last表示

这道题的矩阵一共有三种变换,起始序列为 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 1,2,3,4,5,6,7,8 1,2,3,4,5,6,7,8,题目给定终止序列,问是否能经过三者变换递达终止序列,如果能,最小的操作次数为多少?
以起始序列为源点,每次变换都能递达其他序列。现在的问题是如何变换矩阵?
起始序列为1 2 3 4 5 6 7 8
经过第一种变换的序列为:8 7 6 5 4 3 2 1
经过第二种变换的序列为:4 1 2 3 6 7 8 5
经过第三种变换的序列为:1 7 2 4 5 3 6 8
不要将序列种的数字理解为序列的数值,将它们理解为原序列的下标。比如这个序列8 7 6 5 4 3 2 1,第一个位置上的数为原序列第8个数,第二个位置上的数为原序列第7个数…
这样理解,矩阵变换就很好实现了

至于说最小字典序输出,只要保证每次bfs扩展时,矩阵按照先进行A变换,再B最后C的扩展顺序即可

#include <iostream>
#include <unordered_map>
#include <string>
#include <queue>
#include <algorithm>
using namespace std;

queue<string> q;
string start = "12345678", ed;
unordered_map<string, int> dis;
unordered_map<string, pair<string, char>> last;
string ops[3] = { "87654321", "41236785", "17245368" };

string change(string t, int op)
{
    string res;
    for (int i = 0; i < 8; ++ i )
        res += t[ops[op][i] - '1'];
    return res;
}

void bfs()
{
    q.push(start);
    dis[start] = 0;
    while (q.size())
    {
        auto t = q.front();
        if (t == ed) return;
        q.pop();
        for (int i = 0; i < 3; ++ i )
        {
            string u = change(t, i);
            if (!dis.count(u))
            {
                dis[u] = dis[t] + 1;
                last[u] = { t, i + 'A' };
                q.push(u);
            }
        }
    }
}

int main()
{
    int x;
    while (cin >> x)
        ed += x + '0';

    bfs();
    printf("%d\n", dis[ed]);
    string ans;
    while (ed != start)
    {
        ans += last[ed].second;
        ed = last[ed].first;
    }
    reverse(ans.begin(), ans.end());
    if (ans.size()) cout << ans << endl;
    return 0;
}

双端队列bfs:175. 电路维修

175. 电路维修 - AcWing题库
image.png

双端队列bfs,针对只有0和1的图
题目用符号表示单元格,思考如何建图
一个单元格中,从左上走到右下,若单元格为\,此时的边权为0,否则为1
从右上走到左下,若单元格为/,此时的边权为0,否则为1
权值为1表示需要转动该单元格,才能通过

那么要从当前单元格递达下一单元格(一共四个),可以将单元格看成图中的两点,边的权重取决于两个单元格的电缆方向
可以先建图,处理出图中所有单元格的关系,然后跑最短路。也可以用双端队列进行bfs

不过用来bfs的不是单元格的坐标,而是接点的坐标,用单元格bfs会比较复杂,不大好考虑。单元格直接的电缆有多种情况,需要枚举多次,容易出错
用接点进行bfs呢,只要判断当前接点于下一节点间能够连通的电缆,将其与单元格的电缆进行比对即可
单元格坐标与节点坐标的关系:与左上角的接点坐标相同

#include <iostream>
#include <cstring>
#include <deque>
using namespace std;

typedef pair<int, int> PII;
const int N = 510;
char g[N][N];
bool st[N][N]; int dis[N][N];

int dx[4] = { 1, 1, -1, -1}, dy[4] = { 1, -1, -1, 1 }; // 下一接点的坐标方向
int dgx[4] = { 0, 0, -1, -1}, dgy[4] = { 0, -1, -1, 0 }; // 下一单元格的坐标方向
char s[10] = "\\/\\/"; // 当前坐标与下一坐标连通的电缆方向
int n, m;

int bfs()
{
    memset(dis, 0x3f, sizeof(dis));
    memset(st, false, sizeof(st));
    deque<PII> q;
    q.push_back({ 0, 0 }); dis[0][0] = 0;
    
    while (q.size())
    {
        auto t = q.front(); q.pop_front();
        int x = t.first, y = t.second; // 当前接点的坐标
        if (st[x][y]) continue;
        st[x][y] = true;
        for (int i = 0; i < 4; ++ i )
        {
            int nx = x + dx[i], ny = y + dy[i];
            if (nx >= 0 && nx <= n && ny >= 0 && ny <= m)
            {
                int gx = x + dgx[i], gy = y + dgy[i];
                int d = dis[x][y] + (s[i] != g[gx][gy]);
                if (dis[nx][ny] > d)
                {
                    dis[nx][ny] = d;
                    if (s[i] != g[gx][gy])  q.push_back({ nx, ny });
                    else q.push_front({ nx, ny });
                }
            }
        }
    }
    return dis[n][m];
}

int main()
{
    
    int T;
    scanf("%d", &T);
    while ( T -- )
    {
        scanf("%d%d", &n, &m); 
        for (int i = 0; i < n; ++ i ) scanf("%s", g[i]);
        int t = bfs();
        if (t == 0x3f3f3f3f) puts("NO SOLUTION");
        else printf("%d\n", t);
    }
    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/887987.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

构建智能医疗未来:人工智能在线上问诊系统开发中的应用

随着人工智能技术的飞速发展&#xff0c;医疗领域也正在逐步迎来一场革命性的变革。其中&#xff0c;人工智能在在线上问诊系统开发中的应用&#xff0c;正为医疗产业带来全新的可能性。本文将深入探讨如何利用代码构建智能医疗未来&#xff0c;以提升线上问诊系统的效率、准确…

CSAPP Lab2:Bomb Lab

说明 6关卡&#xff0c;每个关卡需要输入一个字符串&#xff0c;通过逆向工程来获取对应关卡的字符串 准备工作 环境 需要用到gdb调试器 apt-get install gdb系统: Ubuntu 22.04 本实验会用到的gdb调试器的指令如下 r或者 run或者run filename 运行程序,run filename就是…

8----代码块

一、行内代码​ 使用一对反引号()来创建行内代码。 如果在行内代码中需要包含反引号本身&#xff0c;可以使用两个反引号对加前后空格来创建。(但是这样的代码块不会进行语法高亮&#xff0c;只是简单地将代码以等宽字体显示) 注&#xff1a;反引号在键盘上位于左上角&#xff…

用Python打造复古风格的游戏:回归8位时代【俄罗斯方块】

大家好&#xff0c;我是辣条&#xff01; 今天带大家来写一个说难不难&#xff0c;说简单也不算接单的复古小游戏&#xff1a;俄罗斯方块游戏&#xff01; 目录 前言&#xff1a;步骤首先接下来然后接下来最后 上代码&#xff1a;总结: 前言&#xff1a; 俄罗斯方块是一款经典…

PyTorch深度学习实战——使用卷积神经网络执行图像分类

PyTorch深度学习实战——使用卷积神经网络执行图像分类 0. 前言1. Fashion-MNIST 数据集图像分类2. 模型测试相关链接 0. 前言 我们已经在《卷积神经网络详解》一节中介绍了传统神经网络在面对图像平移时的问题以及卷积神经网络 (Convolutional Neural Network, CNN) 的工作原…

CSS 字体修饰属性

前言 字体修饰属性 属性说明font-family指定文本显示字体font-size设置字体的大小font-weight设置字体的粗细程度font-style设置字体的倾斜样式text-decoration给文本添加装饰线text-indent设置文本的缩进text-align设置文本的对齐方式line-height设置行高color设置文本的颜色…

IDEA常用插件推荐(个人)

分享下个人在大厂工作四五年的一个常用配置插件 一、Alibaba Java Coding Guidelines 代码规范插件(必备) 阿里巴巴代码规范检查 人手必备。减少你的垃圾代码 各种不良提示代码全靠它了。 代码划线的嘎嘎 crtlenter优化得了 二、Atom Material File Icons 图标主题插件(提示…

Java学习手册——第二篇面向对象程序设计

Java学习手册——第二篇面向对象 1. 结构化程序设计2. 面向对象 第一章我们已经介绍了Java语言的基础知识&#xff0c;也知道他能干什么了&#xff0c; 那我们就从他的设计思想开始入手吧。 接触一个语言之前首先要知道他的大方向&#xff0c;设计思想是什么样的&#xff0c; 这…

【高阶数据结构】红黑树详解

文章目录 前言1. 红黑树的概念及性质1.1 红黑树的概念1.2 红黑树的性质1.3 已经学了AVL树&#xff0c;为啥还要学红黑树 2. 红黑树结构的定义3. 插入&#xff08;仅仅是插入过程&#xff09;4. 插入结点之后根据情况进行相应调整4.1 cur为红&#xff0c;p为红&#xff0c;g为黑…

Redis——哨兵模式(docker部署redis哨兵)+缓存穿透和雪崩

哨兵模式 自动选取主机的模式。 概述 主从切换技术的方法是:当主服务器宕机后&#xff0c;需要手动把一台从服务器切换为主服务器&#xff0c;这就需要人工干预&#xff0c;费事费力&#xff0c;还会造成段时间内服务不可用。这不是一种推荐的方式&#xff0c;更多时候&…

LabVIEW调用DLL传递结构体参数

LabVIEW 中调用动态库接口时&#xff0c;如果是值传递的结构体&#xff0c;可以根据字段拆解为多个参数&#xff1b;如果参数为结构体指针&#xff0c;可用簇&#xff08;Cluster&#xff09;来匹配&#xff0c;其内存连续相当于单字节对齐。 1.值传递 接口定义&#xff1a; …

交叉导轨的内部结构

相对于直线导轨&#xff0c;交叉导轨的知名度是没那么高的&#xff0c;但随着技术水平的提高&#xff0c;精度更高&#xff0c;安装高度更低的交叉导轨也慢慢走近大众的视野&#xff0c;得到更多厂商的青睐&#xff0c;使用范围也更加广泛。 交叉导轨是由两根具有V型滚道的导轨…

数据结构之动态内存管理机制

目录 数据结构之动态内存管理机制 占用块和空闲块 系统的内存管理 可利用空间表 分配存储空间的方式 空间分配与回收过程产生的问题 边界标识法管理动态内存 分配算法 回收算法 伙伴系统管理动态内存 可利用空间表中结点构成 分配算法 回收算法 总结 无用单元收…

leetcode-413. 等差数列划分(java)

等差数列划分 leetcode-413. 等差数列划分题目描述双指针 上期经典算法 leetcode-413. 等差数列划分 难度 - 中等 原题链接 - 等差数列划分 题目描述 如果一个数列 至少有三个元素 &#xff0c;并且任意两个相邻元素之差相同&#xff0c;则称该数列为等差数列。 例如&#xff0…

【Linux操作系统】Linux系统编程实现递归遍历目录,详细讲解opendir、readdir、closedir、snprintf、strcmp等函数的使用

在Linux系统编程中&#xff0c;经常需要对目录进行遍历操作&#xff0c;以获取目录中的所有文件和子目录。递归遍历目录是一种常见的方法&#xff0c;可以通过使用C语言来实现。本篇博客将详细介绍如何使用C语言实现递归遍历目录的过程&#xff0c;并提供相应的代码示例&#x…

高阶数据结构-图

高阶数据结构-图 图的表示 图由顶点和边构成&#xff0c;可分为有向图和无向图 邻接表法 图的表示方法有邻接表法和邻接矩阵法&#xff0c;以上图中的有向图为例&#xff0c;邻接表法可以表示为 A->[(B,5),(C,10)] B->[(D,100)] C->[(B,3)] D->[(E,7)] E->[…

AgentBench::AI Agent 是大模型的未来

最有想象力、最有前景的方向 “Agent 是 LLM(大语言模型)的最有前景的方向。一旦技术成熟,短则几个月,长则更久,它可能就会创造出超级个体。这解释了我们为何对开源模型和 Agent 兴奋,即便投产性不高,但是我们能想象自己有了 Agent 之后就可以没日没夜地以百倍效率做现在…

Collada .dae文件格式简明教程【3D】

当你从互联网下载 3D 模型时&#xff0c;可能会在格式列表中看到 .dae 格式。 它是什么&#xff1f; 推荐&#xff1a;用 NSDT编辑器 快速搭建可编程3D场景。 1、Collada DAE概述 COLLADA是COLLAborative Design Activity&#xff08;中文&#xff1a;协作设计活动&#xff09…

剑指offer43.1~n整数中1出现的次数

看到这么大的数据规模就直到用暴力法肯定会超时&#xff0c;但是还是花一分钟写了一个试一下&#xff0c;果然超时 class Solution {public int countDigitOne(int n) {int count 0;for(int i1;i<n;i){countdigitOneInOneNum(i);}return count;}public int digitOneInOneNu…

从零实战SLAM-第九课(后端优化)

在七月算法报的班&#xff0c;老师讲的蛮好。好记性不如烂笔头&#xff0c;关键内容还是记录一下吧&#xff0c;课程入口&#xff0c;感兴趣的同学可以学习一下。 --------------------------------------------------------------------------------------------------------…