详解C++图搜索算法之双端队列广搜

2022-11-13 10:11:12 算法 队列 详解

广度优先遍历

广度优先遍历是一种按照层次顺序进行访问的方法,它具有以下两种重要性质:

  • 在访问完所有第i层的结点后,才会去访问第i+1层的结点
  • 任意时刻,队列中至多有两个层次的结点。若其中一部分结点属于第i层,则另一部分结点属于第i+1层,并且所有第i层结点排在第i+1层之前。也就是说,广度优先遍历队列中的元素关于层次满足

双端队列BFS

在最基本的广度优先搜索中,每次沿着分支的扩展都记为“一步”,我们通过逐层搜索,解决了从起始状态到每个状态的最小步数的问题。这其实等价于在一张边权均为1的图上执行广度优先遍历,求出每个点相对于起点的最短距离(层次)。

由于广度优先遍历具有“两段性”和“单调性”,从而我们可以得知,每个状态在第一次被访问并且入队时,计算出的步数即为所求的最短步数。

当出现边权不是0就是1的时候,可以考虑采用双端队列BFS的方法来进行求解。

基本思路:

  • 如果拓展出来的点的边权是0的话,就添加到队头
  • 如果拓展出来的点的边权是1的话,就添加到队尾

正确性:

在通过上述的方式添加元素后,队列仍然能够满足“两段性”和“单调性”,所以所求的结果即为最短路(层次)。

例题:AcWing 175. 电路维修

达达是来自异世界的魔女,她在漫无目的地四处漂流的时候,遇到了善良的少女翰翰,从而被收留在地球上。

翰翰的家里有一辆飞行车。

有一天飞行车的电路板突然出现了故障,导致无法启动。

电路板的整体结构是一个 RR 行 CC 列的网格(R,C≤500R,C≤500),如下图所示。

每个格点都是电线的接点,每个格子都包含一个电子元件。

电子元件的主要部分是一个可旋转的、连接一条对角线上的两个接点的短电缆。

在旋转之后,它就可以连接另一条对角线的两个接点。

电路板左上角的接点接入直流电源,右下角的接点接入飞行车的发动装置。

达达发现因为某些元件的方向不小心发生了改变,电路板可能处于断路的状态。

她准备通过计算,旋转最少数量的元件,使电源与发动装置通过若干条短缆相连。

不过,电路的规模实在是太大了,达达并不擅长编程,希望你能够帮她解决这个问题。

注意:只能走斜向的线段,水平和竖直线段不能走。

输入格式

输入文件包含多组测试数据。

第一行包含一个整数 TT,表示测试数据的数目。

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

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

输出格式

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

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

数据范围

1≤R,C≤5001≤R,C≤500,

1≤T≤51≤T≤5

输入样例

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

输出样例

1

样例解释

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

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

解题思路

如图所示,用红圈所圈起来的点都是从起点出发所不能到达的(沿对角线行走的情况下)

从起点出发所能达到的点的坐标之和应为偶数,图中所圈出来的点的坐标之和均为奇数。

所以我们第一步可以使用这个性质来判断终点是否能够到达。 

然后使用双端队列来进行解答,在这道题目中对于格子和点的对应关系需要进行思考。

将电路板上的每个格点(横线和竖线的交叉点)看做是无向图中的结点。如果对角线和x到y的线段重合,则边权为0;若不重合,则边权为1。

然后在图中求出从左上角到右下角的最短距离。

踩过格子到达想去的点时,需要判断是否需要旋转电线。

若旋转电线则表示从当前点到想去的点的边权是1,若不旋转电线则边权为0。

  • dx[]和dy[]表示可以去其他点的方向
  • ix[]和iy[]表示需要踩某个方向的点才能去到相应的点
  • cs[]表示当前点走到4个方向的点理想状况下格子形状(边权是0的状态)

AC代码

#include<iOStream>
#include<deque>
#include<cstring>
#include<alGorithm>
 
#define x first
#define y second
using namespace std;
typedef pair<int, int> PII;
const int N = 510;
 
int n,m;
char g[N][N];
int dist[N][N];;
deque<PII> q;
 
int bfs()
{
    memset(dist,0x3f,sizeof dist);
    q.push_front({0,0});
    dist[0][0]=0;
    int dx[]={-1,-1,1,1},dy[]={-1,1,1,-1};
    int ix[]={-1,-1,0,0},iy[]={-1,0,0,-1};
 
    char s[]="\\/\\/";
 
    while(q.size())
    {
        PII t=q.front();
        q.pop_front();
        for(int i=0;i<4;i++)
        {
            int a=t.x+dx[i],b=t.y+dy[i];
            int aa=t.x+ix[i],bb=t.y+iy[i];
            if(a<0||a>n||b<0||b>m) continue;
            int d = dist[t.x][t.y]+(g[aa][bb]!=s[i]);
            if (d < dist[a][b])
            {
                dist[a][b] = d;
 
                if (g[aa][bb] != s[i]) q.push_back({a, b});
                else q.push_front({a, b});
            }
        }
    }
    return dist[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]);
        if((n+m)%2==1) 
        {
            puts("NO SOLUTION");
            continue;
        }                
        cout<<bfs()<<endl;
    }
    return 0;
}

每个结点虽然可能被更新(入队)多次,但是它第一次被拓展(出队)时,就能得到从左上角到该点的最短距离,之后再取出可以直接忽略。

时间复杂度是O(M * N)

以上就是详解c++图搜索算法之双端队列广搜的详细内容,更多关于C++双端队列广搜的资料请关注其它相关文章!

相关文章