题目链接:点击这里
我们可以把电路板上的每个格点(横线与竖线的交叉点)看作无向图中的节点。若两个节点 和 是某个小方格的两个对角,则在 与 之间连边。若该方格中的标准件(对角线)与 到 的线段重合,则边权为 ;若垂直相交,则边权为 (说明需要旋转 次才能连上)。然后,我们在这个无向图中求出左上角到右下角的最短距离,就得到了答案。
这是一张边权要么是 0、要么是 1 的无向图。在这样的图上,我们可以通过双端队列广搜来计算。算法的整体框架与一般的广搜类似,只是在每个节点上沿分支扩展时稍作改变。如果这条分支是边权为0 的边,就把沿该分支到达的新节点从队头入队;如果这条分支是边权为 1 的边,就像一般的广搜一样从队尾入队。这样一来,我们就仍然能保证,任意时刻广搜队列中的节点对应的距离值都具有“两段性”和“单调性”,每个节点第一次被访问时,就能得到从左上角到该节点的最短距离。
因为每个节点只需要访问一次,所以算法的时间复杂度为 。
本题还有一个性质,其实网格格点有一半是永远走不到的(如下图红色的点),所以可以特判一下。
细节处理:每个格子的编号和每个格点的编号不一样,注意两对增量数组的写法。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<deque>
#include<algorithm>
using namespace std;
typedef pair<int,int> PII;
const int N = 510;
int dx[] = {-1, -1, 1, 1}; // 当前格点四周的新格点
int dy[] = {-1, 1, 1, -1};
int ix[] = {-1, -1, 0, 0}; // 当前格点四周的格子
int iy[] = {-1, 0, 0, -1};
char cs[5] = "\\/\\/";
int n, m;
char g[N][N];
int d[N][N];
bool st[N][N]; // 存储每个点的最短路是否已经确定
int bfs()
{
deque<PII> q;
memset(d, 0x3f, sizeof d);
memset(st, false, sizeof st);
q.push_back({0, 0});
d[0][0] = 0;
while(q.size())
{
auto t = q.front();
q.pop_front();
int x = t.first, y = t.second; // (x,y)简化后续代码
if(x == n && y == m) return d[x][y];
if(st[x][y]) continue;
st[x][y] = true;
for(int i = 0; i < 4; ++i)
{
int a = x + dx[i], b = y + dy[i]; // (x,y)扩展出的新格点(a,b)
if(a < 0 || a > n || b < 0 || b > m) continue;
int ga = x + ix[i], gb = y + iy[i]; // (x,y)扩展出的格子(ga,gb)
int dist = d[x][y] + (g[ga][gb] != cs[i]);
if(dist < d[a][b])
{
d[a][b] = dist;
if(g[ga][gb] != cs[i]) q.push_back({a, b});
else q.push_front({a, b});
}
}
}
return -1; // 不会被执行到
}
int main()
{
int T;
scanf("%d", &T);
while(T--)
{
scanf("%d%d", &n, &m); // n行m列的格子
for(int i = 0; i < n; ++i) scanf("%s", g[i]);
if((n + m) & 1) puts("NO SOLUTION");
else printf("%d\n", bfs());
}
return 0;
}