题目地址:
https://www.acwing.com/problem/content/177/
达达是来自异世界的魔女,她在漫无目的地四处漂流的时候,遇到了善良的少女翰翰,从而被收留在地球上。翰翰的家里有一辆飞行车。有一天飞行车的电路板突然出现了故障,导致无法启动。电路板的整体结构是一个 R R R行 C C C列的网格( R , C ≤ 500 R,C≤500 R,C≤500),如下图所示。
每个格点都是电线的接点,每个格子都包含一个电子元件。电子元件的主要部分是一个可旋转的、连接一条对角线上的两个接点的短电缆。在旋转之后,它就可以连接另一条对角线的两个接点。电路板左上角的接点接入直流电源,右下角的接点接入飞行车的发动装置。达达发现因为某些元件的方向不小心发生了改变,电路板可能处于断路的状态。她准备通过计算,旋转最少数量的元件,使电源与发动装置通过若干条短缆相连。不过,电路的规模实在是太大了,达达并不擅长编程,希望你能够帮她解决这个问题。注意:只能走斜向的线段,水平和竖直线段不能走。
输入格式:
输入文件包含多组测试数据。第一行包含一个整数 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 1≤R,C≤500
1 ≤ T ≤ 5 1≤T≤5 1≤T≤5
思路是用双端队列来做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)。