/*
抽象成一个最短路,横纵坐标和为奇数的点,永远走不到
每一步的长度为0或1,1表示需要反转一下
dx,dy确定点的坐标,ix,iy确定线的坐标
在读入数据的时候,我们输入的是线,但是我们的BFS是按照点来做的
所以向四个方向扩展时,每一个点要找对应4个方向的线,看看走到对角线上点的位置
需要0还是1
*/
#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};//表示可以走到的四个点的坐标
int ix[]={-1, -1, 0, 0}, iy[]={-1, 0, 0, -1};// 把格子上的点和线分开看
//i,j位置的点,对应的走到对角线上距离为0的线,在g中的坐标应该是
//左上:i-1,j-1,右上:i-1, j 右下:i,j 左下:i,j-1
//这个在bfs从一个点往下继续扩展时会用到
while(q.size())
{
PII t=q.front();
q.pop_front();//删掉对头
if(st[t.x][t.y]) continue;//扩展过了,就直接跳过,
//所有边的权重不同,所以某些点可能需要被扩展多次。
// 这道题目可以看成是特殊的dijkstra算法,在dijkstra算法中,
// 某些点可能会被多次扩展,但第一次从优先队列中弹出的节点的
// 距离一定就是最小值了,所以需要在出队的时候来判重
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);
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;
}
“只有这个点第一次从堆中出来的时候,也就是当前点是当前堆中最小值时,它的值才一定是最优解。”大佬这句话怎么理解??