草庐IT

「双端队列BFS」电路维修

孤星·璀璨 2023-03-28 原文

本题为3月23日23上半学期集训每日一题中B题的题解

题面

题目描述

Ha'nyu是来自异世界的魔女,她在漫无目的地四处漂流的时候,遇到了善良的少女Rika,从而被收留在地球上。Rika的家里有一辆飞行车。有一天飞行车的电路板突然出现了故障,导致无法启动。

电路板的整体结构是一个R行C列的网格( \(R,C \leq 500\) ),如右图所示。每个格点都是电线的接点,每个格子都包含一个电子元件。电子元件的主要部分是一个可旋转的、连接一条对角线上的两个接点的短电缆。在旋转之后,它就可以连接另一条对角线的两个接点。电路板左上角的接点接入直流电源,右下角的接点接入飞行车的发动装置。

Ha'nyu发现因为某些元件的方向不小心发生了改变,电路板可能处于断路的状态。她准备通过计算,旋转最少数量的元件,使电源与发动装置通过若干条短缆相连。不过,电路的规模实在是太大了,Ha'nyu并不擅长编程,希望你能够帮她解决这个问题。

输入

输入文件包含多组测试数据。第一行包含一个整数T 表示测试数据的数目。

对于每组测试数据,第一行包含正整数R 和C,表示电路板的行数和列数。

之后R 行,每行C 个字符,字符是"/"和""中的一个,表示标准件的方向。

输出

对于每组测试数据,在单独的一行输出一个正整数,表示所需的缩小旋转次数。

如果无论怎样都不能使得电源和发动机之间连通,输出NO SOLUTION。

样例输入

1
3 5
\\/\\
\\///
/\\\\

样例输出

1

提示

样例的输入对应于题目描述中的情况。

只需要按照下面的方式旋转标准件,就可以使得电源和发动机之间连通。

思路分析

本题要求的是转弯数的最小值,在图论中我们常常可求路径的最小值,所以我们尝试把旋转次数定义为"路径长",那么本题题意可以转化为,当从一个点移动到另一个点时,如果电路本身是通的,边权为0,否者边权为1,求起点到终点的最短路径.

转化后的此题类似于洛谷上第4554题的升级版,我们可以使用同样的思路解决此问题,即双端队列BFS(有人也叫它01BFS).

关于双端队列BFS,我之前一道题目的题解中从用来解最短路问题BFS的角度出发进行过详细的分析,感兴趣可以去看一看,在这里我从Dijkstra算法的角度对它进行一些另一个视角的分析,相信你将会发现各个最短路算法之间的高度统一性.

我们知道,Dijkstra算法的贪心策略是,每次选择当前未确定的点中到起点距离最短的点,从这个点出发继续进行操作.而为了减少这个找最小值过程所花费的时间,我们采用优先队列进行优化.

此题当然也可以直接用Dijkstra算法来解决,因为此题没有负权边.但是我们其实没有必要完完全全按照Dijkstra算法来进行操作.我们注意到此处边权只有0和1,如果我们当前队列中是有序存放的,那么加上0边权之后,它依旧是最短的,我们可以直接把它放到队头;而加上1边权之后,又如何呢?由于队列是有序的,且边权是0和1,而加上0边权的全部放到了队首,加了1边权的一定会在0边权的全部处理完之后才会处理.所以队列里面同时一定只存在两种路径长度,且这两种长度差距一定是1.所以当前加上1边权之后,我们可以直接把它放到队尾.而初始的时候,队列里面只有起点到起点的距离(0),这是有序的(虽然只有一个元素,但是你就说它有序不有序吧),所以我们可以按照上述的方式来维护距离,即:

  • 如果当前边权是0,我们把它放到队首
  • 如果当前边权非0,我们把它放在队尾

由此,我们可以把优先队列换成一个双端队列,来优化掉优先队列的对数时间(你不会以为优先队列是常数时间吧?不会吧不会吧?).此时我们所写的这种最短路算法,就称之为双端队列BFS.所以从对Dijkstra算法优化的角度来看,所谓双端队列BFS就是在图上边权只有两种,且一种为0时的特殊优化.因为此时通过一定的操作,可以手动维护队列有序,所以可以优化掉那个需要log时间的优先队列,来减少时间消耗.

所以此题如何求解答案(即最短路)的方法已经确定了,我们只要把Dijkstra算法中插入优先队列的过程换成上面那两条规则即可.接下来我们的问题就是,如何确定边权.根据题目意思,格点对应的是图上的点,而点只能斜向移动到另一个点.所以我们只要看当前点向四个斜方向的边是什么形状即可.这里有一个难点是如何确定边的坐标,其实画一个图就很清晰了:

四个方向的坐标变换就是(1,1),(-1,1),(1,-1),(-1,-1),如果记原本的点坐标为(_x,_y),变换后的点坐标为(x,y),对应的边的坐标就分别是(_x,_y),(x,_y),(_x,y),(x,y),我们直接套用这个规律即可.

参考代码

时间复杂度: \(O(RC)\)

空间复杂度: \(O(RC)\)

#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3, "Ofast", "inline")
#include <bits/stdc++.h>

using namespace std;

using i64 = long long;

int book[][2] = {{1, 1}, {-1, 1}, {1, -1}, {-1, -1}}; // 对角线四个方向

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

    int t;
    cin >> t;
    while (t--) {
        int r, c;
        cin >> r >> c;

        vector<string> g(r); // 存边的形状
        for (auto &i : g) {
            cin >> i;
        }

        // 双端队列BFS(改造优先队列优化BFS,无注释的部分为Dijkstra本身的内容)
        deque<pair<int, int>> deq; // 双端队列(代替优先队列)
        vector<vector<int>> dist(r + 1, vector<int>(c + 1, 0x3f3f3f3f));
        deq.push_back({0, 0});
        dist[0][0] = 0;
        while (!deq.empty()) {
            auto p = deq.front();
            deq.pop_front();
            int _x = p.first;
            int _y = p.second;

            // 遍历其相邻格点(4个斜方向)
            for (int i = 0; i < 4; i++) {
                int x = _x + book[i][0];
                int y = _y + book[i][1];

                if (x >= 0 && x <= r && y >= 0 && y <= c) { // 防止越界
                    int c = 1; // 默认边权为1

                    // 检查原本的边的方向(看上面的图找个规律就好)
                    if (i == 0 && g[_x][_y] == '\\') { // 反斜杠记得转义
                        c = 0;
                    } else if (i == 1 && g[x][_y] == '/') {
                        c = 0;
                    } else if (i == 2 && g[_x][y] == '/') {
                        c = 0;
                    } else if (i == 3 && g[x][y] == '\\') {
                        c = 0;
                    }

                    if (dist[_x][_y] + c < dist[x][y]) {
                        dist[x][y] = dist[_x][_y] + c;
                        if (c) { // 当前边权非0,放入队尾
                            deq.push_back({x, y});
                        } else { // 当前边权为0,放入队首
                            deq.push_front({x, y});
                        }
                    }
                }
            }
        }

        if (dist[r][c] != 0x3f3f3f3f) { // 可达
            cout << dist[r][c] << "\n";
        } else { // 不可达
            cout << "NO SOLUTION\n";
        }
    }
    return 0;
}

"正是我们每天反复做的事情,最终造就了我们,优秀不是一种行为,而是一种习惯" ---亚里士多德

有关「双端队列BFS」电路维修的更多相关文章

  1. ruby - 分布式事务和队列,ruby,erlang,scala - 2

    我有一个涉及多台机器、消息队列和事务的问题。因此,例如用户点击网页,点击将消息发送到另一台机器,该机器将付款添加到用户的帐户。每秒可能有数千次点击。事务的所有方面都应该是容错的。我以前从未遇到过这样的事情,但一些阅读表明这是一个众所周知的问题。所以我的问题。我假设安全的方法是使用两阶段提交,但协议(protocol)是阻塞的,所以我不会获得所需的性能,我是否正确?我通常写Ruby,但似乎Redis之类的数据库和Rescue、RabbitMQ等消息队列系统对我的帮助不大——即使我实现某种两阶段提交,如果Redis崩溃,数据也会丢失,因为它本质上只是内存。所有这些让我开始关注erlang和

  2. LC滤波器设计学习笔记(一)滤波电路入门 - 2

    目录前言滤波电路科普主要分类实际情况单位的概念常用评价参数函数型滤波器简单分析滤波电路构成低通滤波器RC低通滤波器RL低通滤波器高通滤波器RC高通滤波器RL高通滤波器部分摘自《LC滤波器设计与制作》,侵权删。前言最近需要学习放大电路和滤波电路,但是由于只在之前做音乐频谱分析仪的时候简单了解过一点点运放,所以也是相当从零开始学习了。滤波电路科普主要分类滤波器:主要是从不同频率的成分中提取出特定频率的信号。有源滤波器:由RC元件与运算放大器组成的滤波器。可滤除某一次或多次谐波,最普通易于采用的无源滤波器结构是将电感与电容串联,可对主要次谐波(3、5、7)构成低阻抗旁路。无源滤波器:无源滤波器,又称

  3. ruby-on-rails - Ruby 长时间运行的进程对队列事件使用react - 2

    我有一个将某些事件写入队列的Rails3应用。现在我想在服务器上创建一个服务,每x秒轮询一次队列,并按计划执行其他任务。除了创建ruby​​脚本并通过cron作业运行它之外,还有其他稳定的替代方案吗? 最佳答案 尽管启动基于Rails的持久任务是一种选择,但您可能希望查看更有序的系统,例如delayed_job或Starling管理您的工作量。我建议不要在cron中运行某些东西,因为启动整个Rails堆栈的开销可能很大。每隔几秒运行一次它是不切实际的,因为Rails上的启动时间通常为5-15秒,具体取决于您的硬件。不过,每天这样做几

  4. ruby - 在不提供其所有属性的情况下获取队列 - 2

    我正在尝试为现有队列编写消费者。RabbbitMQ在一个单独的实例中运行,名为“org-queue”的队列已经创建并绑定(bind)到一个交换器。org-queue是一个持久队列,它还有一些额外的属性。现在我需要从这个队列接收消息。我使用下面的代码来获取队列的实例conn=Bunny.newconn.startch=conn.create_channelq=ch.queue("org-queue")它抛出一个错误,指出不同的耐用属性。默认情况下,Bunny似乎使用durable=false。所以我添加了durabletrue作为参数。现在它说明了其他参数之间的区别。我是否需要指定所有参

  5. ruby - 如何在特定队列中推送作业并使用 sidekiq 限制工作人员数量? - 2

    我知道我们可以做到:sidekiq_optionsqueue:"Foo"但在这种情况下,Worker只分配给一个队列:“Foo”。我需要在特定队列中分配作业(而不是worker)。使用Resque很容易:Resque.enqueue_to(queue_name,my_job)另外,为了并发问题,我需要限制每个队列的Worker数量为1。我该怎么做? 最佳答案 您可能会使用https://github.com/brainopia/sidekiq-limit_fetch然后:Sidekiq::Client.push({'class'=>

  6. Python:每日一题之小张的衣服(优先队列、哈夫曼编码) - 2

    题目描述小张买了 n 件白色的衣服,他觉得所有衣服都是一种颜色太单调,希望对这些衣服进行染色,每次染色时,他会将某种颜色的所有衣服寄去染色厂,第 i 件衣服的邮费为 ai​ 元,染色厂会按照小张的要求将其中一部分衣服染成同一种任意的颜色,之后将衣服寄给小张,请问小张要将 n 件衣服染成不同颜色的最小代价是多少?输入描述第一行为一个整数 n ,表示衣服的数量。第二行包括 n 个整数a1​,a2​...an​ 表示第 i 件衣服的邮费为 ai​ 元。(1≤n≤10^5,1≤ai​≤10^9 )输出描述输出一个整数表示小张所要花费的最小代价。输入输出样例输入551321输出25 思考🤔:题意:意思是

  7. 1个串口用1根线实现多机半双工通信+开机控制电路 - 2

    功能需求:主机使用一个串口,与两个从机进行双向通信,主机向从机发送数据,从机能够返回数据,由于结构限制,主机与从机之间只有3根线(电源、地、数据线),并且从机上没有设物理的电源开关,需要通过与主机连接的数据线来控制开机,总结如下:1、数据线只有1根2、能够双向通信3、主机能够控制从机开机4、主机可以单独向1个从机发数据,也可以同时向两个从机发送数据根据需求,设计出如下电路:工作原理分析:VCC_24V_IN、GND、LINE_L(LINE_R)三根线接线连接到从机,电源开启电路是从机内部的电源控制。开机的逻辑:*主机先上电,LINE_L因为主机的R1上拉而有高电平,使Q6导通,Q5的G极电压被

  8. NEUQ-acm 预备队训练Week4—BFS/DFS - 2

    1.深度优先搜索(DFS)深度优先遍历主要思路是从图中一个未访问的顶点V开始,沿着一条路一直走到底,然后从这条路尽头的节点回退到上一个节点,再从另一条路开始走到底…,不断递归重复此过程,直到所有的顶点都遍历完成。例题P1605迷宫题目描述给定一个N×MN\timesMN×M方格的迷宫,迷宫里有TTT处障碍,障碍处不可通过。在迷宫中移动有上下左右四种方式,每次只能移动一个方格。数据保证起点上没有障碍。给定起点坐标和终点坐标,每个方格最多经过一次,问有多少种从起点坐标到终点坐标的方案。输入格式第一行为三个正整数N,M,TN,M,TN,M,T,分别表示迷宫的长宽和障碍总数。第二行为四个正整数SX,S

  9. 常见搜索模板DFS+BFS+二分搜索【算法】 - 2

     本篇讲的是常见的搜索模板,搜索题的解法时比较固定的,只要把模板记熟,加上自己找几道习题练习体会后,相信各位下次遇到这类题一定能拿下!!下面我将已典型的题目为例子介绍几种常见的搜索方式。 1.二分搜索二分搜索代码模板:例题:#includeusingnamespacestd;doublen;constdoubleeps=1e-12;//二分搜索intmain(){ intt; cin>>t; while(t--){ cin>>n; doublel=0,r=100000,res=-1; while(ln)r=mid-0.0001; elseif(mid*mid*mid二分搜索是只能对有

  10. ruby - Resque:每个队列一个 worker - 2

    我目前有一个Rails3.0项目,使用Ruby1.9.2和Resque。我的应用程序有多个工作类和多个队列,它们是动态创建的(在运行时)。此外,有多个worker已启动,可以自由地在任何队列上工作,因为在启动时没有任何现有队列,并且无法预测它们:$COUNT=3QUEUE=*rakeresque:workers根据project的id创建队列:@queue="project_#{project.id}".to_sym对于给定的队列,他们的作业必须按顺序处理,一次处理一个。我的问题是,通过拥有多个工作人员,可以并行处理多个作业。有没有办法设置每个队列的最大worker数(为1)?有没有办

随机推荐