【ACWing】175. 电路维修

题目地址:

https://www.acwing.com/problem/content/177/

达达是来自异世界的魔女,她在漫无目的地四处漂流的时候,遇到了善良的少女翰翰,从而被收留在地球上。翰翰的家里有一辆飞行车。有一天飞行车的电路板突然出现了故障,导致无法启动。电路板的整体结构是一个 R R R C C C列的网格( R , C ≤ 500 R,C≤500 R,C500),如下图所示。
在这里插入图片描述

每个格点都是电线的接点,每个格子都包含一个电子元件。电子元件的主要部分是一个可旋转的、连接一条对角线上的两个接点的短电缆。在旋转之后,它就可以连接另一条对角线的两个接点。电路板左上角的接点接入直流电源,右下角的接点接入飞行车的发动装置。达达发现因为某些元件的方向不小心发生了改变,电路板可能处于断路的状态。她准备通过计算,旋转最少数量的元件,使电源与发动装置通过若干条短缆相连。不过,电路的规模实在是太大了,达达并不擅长编程,希望你能够帮她解决这个问题。注意:只能走斜向的线段,水平和竖直线段不能走。

输入格式:
输入文件包含多组测试数据。第一行包含一个整数 T T T,表示测试数据的数目。对于每组测试数据,第一行包含正整数 R R R C C C,表示电路板的行数和列数。之后 R R R行,每行 C C C个字符,字符是"/""\"中的一个,表示标准件的方向。

输出格式:
对于每组测试数据,在单独的一行输出一个正整数,表示所需的缩小旋转次数。如果无论怎样都不能使得电源和发动机之间连通,输出NO SOLUTION

数据范围:
1 ≤ R , C ≤ 500 1≤R,C≤500 1R,C500
1 ≤ T ≤ 5 1≤T≤5 1T5

思路是用双端队列来做BFS。将每个横平竖直的边的交叉点看成是图的顶点,斜着的边是图的边(这里需要假设每两个相距 2 \sqrt 2 2 的顶点之间都是有边的),这样问题就转化为一个图论问题。进一步地,如果当两个顶点之间有电缆(即电缆和这两个顶点之间的边是重合的),视为图里的这条边边权是 0 0 0(代表不需要旋转),否则视为图里的这条边边权是 1 1 1(代表需要旋转)。这样问题就转化为,从 ( 0 , 0 ) (0,0) (0,0)这个顶点到 ( n , m ) (n,m) (n,m)这个顶点的最短路的长度是多少。经典做法是用Dijkstra算法,但这里并不需要用最小堆,而可以将其改为双端队列,如果某个顶点是权为 0 0 0的边拓展的,那么就将这个顶点入队头;否则入队尾。别的部分都与Dijkstra算法一样去做。这样其实本质是还是在模仿Dijkstra算法,所以算法正确性是可以保证的。

这里还有个问题需要注意,有没有可能某个电缆被旋转了两次呢。这是不可能的。因为起点是 ( 0 , 0 ) (0,0) (0,0),而每次走一条边,都会使得两坐标之和 x + y x+y x+y变化 − 2 , 0 , + 2 -2,0,+2 2,0,+2,所以 x + y x+y x+y永远都是偶数,而如果某个电缆被旋转两次的话,就说明走到了 x + y x+y x+y是奇数的顶点,这是不可能的。同时我们也得到了一个结论,就是如果 n + m n+m n+m是奇数,那么一定无解。反之,如果 n + m n+m n+m是偶数,则一定有解,因为直接把主对角线都旋转为\,再适当调整别的电缆使得能走到终点,这就是一个解。

代码如下:

#include <iostream>
#include <cstring>
#include <deque>

using namespace std;

const int N = 510;

int n, m;
char g[N][N];
// 这里的dist和st数组都直接模仿Dijkstra算法
int dist[N][N];
bool st[N][N];

int bfs() {
    
    
    memset(st, false, sizeof st);
    memset(dist, 0x3f, sizeof dist);

    string s = "\\/\\/";
    int dx[] = {
    
    -1, -1, 1, 1}, dy[] = {
    
    -1, 1, 1, -1};
    int ix[] = {
    
    -1, -1, 0, 0}, iy[] = {
    
    -1, 0, 0, -1};

    deque<pair<int, int> > dq;
    dq.push_back({
    
    0, 0});
    dist[0][0] = 0;

    while (!dq.empty()) {
    
    
        auto t = dq.front();
        dq.pop_front();

        int x = t.first, y = t.second;

        if (x == n && y == m) return dist[x][y];

        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 (0 <= nx && nx <= n && 0 <= ny && ny <= m) {
    
    
            	// 求一下当前顶点到(nx, ny)这个顶点的边在g数组里的下标
                int gx = x + ix[i], gy = y + iy[i];
                // 求一下该边的权重
                int w = (g[gx][gy] != s[i]);
                int d = dist[x][y] + w;
                if (d < dist[nx][ny]) {
    
    
                    dist[nx][ny] = d;
                    if (w) dq.push_back({
    
    nx, ny});
                    else dq.push_front({
    
    nx, ny});
                }
            }
        }
    }

    return -1;
}

int main() {
    
    
    int T;
    cin >> T;
    while (T--) {
    
    
        cin >> n >> m;
        
        for (int i = 0; i < n; i++)
            for (int j = 0; j < m; j++)
                cin >> g[i][j];

        if ((n + m) % 2) cout << "NO SOLUTION" << endl;
        else cout << bfs() << endl;
    }

    return 0;
}

时空复杂度 O ( R C ) O(RC) O(RC)

猜你喜欢

转载自blog.csdn.net/qq_46105170/article/details/114667171