一个重要性质:横纵坐标和为奇数的点,永远走不到,反转不会影响这个性质。
dx,dy确定点的坐标,ix,iy确定线的坐标
在读入数据的时候,我们输入的是线,但是我们的BFS是按照点来做的
所以向四个方向扩展时,每一个点要找对应4个方向的线, 看看走到对角线上点的位置需要0还是1
每一步的长度为0或1,1表示需要反转一下
真正的BFS:第一次遍历到的时候就是最小值了。
真正的双端队列:第一次遍历的时候不一定是最小值,但是第一次出队的时候一定是最小值了。
这道题目可以看成是特殊的dijkstra算法,在dijkstra算法中,
某些点可能会被多次扩展,但第一次从优先队列中弹出的节点的
距离一定就是最小值了,所以需要在出队的时候来判重
由于图中没有边权为负数的点, 所以dijkstra一定可以做. 又由于图中的边权只有0和1, 所以堆优化版的dijkstra中的堆,就可以用双端队列来实现.
图源:https://www.acwing.com/solution/content/21775/
#include<cstring>
#include<iostream>
#include<algorithm>
#include<deque>//双端队列
#define x first
#define y second
using namespace std;
typedef pair<int, int> PII;
const int N=510, M=N*N;
int n, m;
char g[N][N];
int dist[N][N];//注意起点是从0,0开始的
bool st[N][N];
int bfs()
{
//由于有多组输入,所以每次都要初始化
memset(dist, 0x3f, sizeof dist);
memset(st, 0, sizeof st);
dist[0][0]=0;
deque<PII> q;
q.push_back({0, 0});
char cs[]="\\/\\/";// 表示某个点可选的走到4个点的花费为0字符,\/\/,顺时针 /的话需要转义字符
int dx[]={-1, -1, 1, 1}, dy[]={-1, 1, 1, -1};//表示当前交叉点可以走到的四个点的坐标 左上 左下 右下 右上
// 当前交叉点要走到上边的那四个点,在g数组中,对应的位置
// 假设当前点的坐标是x,y. 则走到x-1, y-1,在g数组中对应的导线就存在g[x-1][y-1];想要走到x-1,y+1,对应的导线存在g[x-1][y];想要走到x+1, y+1, 对应的导线存在g[x][y]......
int ix[]={-1, -1, 0, 0}, iy[]={-1, 0, 0, -1};
while(q.size())
{
PII t=q.front();
q.pop_front();//删掉对头
//所有边的权重不同,所以某些点可能需要被扩展多次。
// 这道题目可以看成是特殊的dijkstra算法,在dijkstra算法中,
// 某些点可能会被多次扩展,但第一次从优先队列中弹出的节点的
// 距离一定就是最小值了,所以需要在出队的时候来判重
if(st[t.x][t.y]) continue;//扩展过了,就直接跳过,
st[t.x][t.y]=true;
for(int i=0; i<4; i++)
{
// 对应交叉点
int a=t.x+dx[i], b=t.y+dy[i];
if(a<0 || a>n || b<0 || b>m) continue;
//判断g中四个方向是什么字符,即看一下走到对角线花费是1还是0
int ca=t.x+ix[i], cb=t.y+iy[i];
//看看像走到的方向与g中给出的导线方向是否相同,与理论方向相同,则加0,不同则加1
int d=dist[t.x][t.y]+(g[ca][cb] != cs[i]);
//每个点可能会被扩展多次,第一次被扩展到的时候不一定是最优解。
//有可能a,b由第一个节点扩展而来,但是距离加了1,但是从第二个节点扩展而来就加0,
//所以第二次扩展的d竟然小于第一次扩展而来的d
// 只有这个点第一次从堆中出来的时候,也就是当前点是当前堆中最小值时,
// 它的值才一定是最优解。这就是一个简化版的dijkstra算法。
if(d<dist[a][b])
{
dist[a][b]=d;
//如果g中的值不等于理论值,则走到需要+1, 所以放在队尾
if(g[ca][cb]!=cs[i]) q.push_back({a, b});
//否则放在对头
else q.push_front({a, b});
}
}
}
return dist[n][m];//这里的n,m就是实打实的n,m
}
int main()
{
int T;
cin>>T;
while(T--)
{
scanf("%d%d", &n, &m);
// 从0行0列开始输入\ 和 /
for(int i=0; i<n; i++) scanf("%s", g[i]);
int t=bfs();
//优化,和是奇数,直接无解
if((n+m)&1) puts("NO SOLUTION");
else printf("%d\n", t);
}
return 0;
}