双端队列广搜
解决问题:
双端队列主要解决图中边的权值只有 0 或者 1 的最短路问题
操作:
每次从队头取出元素,并进行拓展其他元素时
- 若拓展某一元素的边权是 0,则将该元素插入到队头
- 若拓展某一元素的边权是 1,则将该元素插入到队尾
与堆优化Dijkstra 一样,必须在出队时才知道每个点最终的最小值,而和一般的bfs不一样,原因是如下图所示
解释:当我们第一次用1号点更新3号点的时候,3号点当前的最短路距离并不是真正的最短距离,并且3号点会放在队尾,会被2号点再次更新,放在队头,当3号点出队的时候,就是真正的最短距离了。(其实看做dijkstra算法就好了)
双端队列广搜求最短路的合法性:
在通常的广搜 bfs 中,边权都为 1 ,这样每次被更新时,新加入队列的点一定更新它的点的 dist 要大,这样就可以满足bfs求最短路的两个条件:
- 两段性
- 单调性
但在这里,由于边权可能为 0 (不需要旋转边时),所以说我们不能总是把被更新的点加入队尾,因为如果这个点是被权值为 0 的边更新,那么它的 dist 是可能比队尾的 dist 小的,所以说此时要把该点加入对头,这样就满足了两段性,也就满足了单调性。
而 deque 就满足了既要加入对头,又加入队尾的操作。
关于题目:
首先明确的是,图中的格子和点是不一样的,题目的输入是格子(或者是边),而我们要找的是从起点 (0,0) 到终点 (n,m) 的一条最短路
按左上角,右上角,右下角,左下角遍历的顺序
- dx[] 和 dy[] 表示可以去其他点的方向
- ix[] 和 iy[] 表示需要踩某个方向的边才能取到相应的点
- ch[] 表示当前点走到 4 个方向的点理想状态下格子形状(边权是 0 的状态)
没有答案的情况
由于我们的每条边都是斜边(题目说明没有横向边和竖向边),那么我们每次走过一条过,x 和 y 的值要么都 +1,要么都 -1。又因为起点为(0,0),所以说,我们每次到达的点的横纵坐标之和为偶数,即 x+y&1=0 ,所有横纵坐标之和为奇数的点我们都无法走到!
时间复杂度 O(nm)
#include <iostream>
#include <cstring>
#include <algorithm>
#include <deque>
#define x first
#define y second
using namespace std;
typedef pair<int, int> PII;
const int N = 550;
char ch[] = {'\\', '/', '\\', '/'};//对应的边,注意\是转义字符,表示\需要两个'\''
//char ch[] = "\\/\\/";也可以这样写,注意ch字符的结尾会有一个'\0'
int dx[4] = {-1, -1, 1, 1}, dy[4] = {-1, 1, 1, -1}; //找点
int ix[4] = {-1, -1, 0, 0}, iy[4] = {-1, 0, 0, -1};//找连接到这个点的边
int T, n, m;
int dist[N][N];
char g[N][N];
bool st[N][N];
int bfs()
{
memset(dist, 0x3f, sizeof dist);
memset(st, false, sizeof st);
deque<PII> q;
q.push_front({0, 0});//放入起点
dist[0][0] = 0;//dist[起点] = 0
while(q.size())
{
auto t = q.front();//去队头(最小值)
q.pop_front();//出队
int a = t.x, b = t.y;
int distance = dist[a][b];
if(a == n && b == m) return dist[n][m];//如果到达终点,返回
if(st[a][b]) continue;//如果被访问过
st[a][b] = true;
for(int i = 0; i < 4; i ++ )
{
int pa = a + dx[i], pb = b + dy[i];//下一个要访问的点
if(pa < 0 || pa > n || pb < 0 || pb > m) continue;//注意x和y可以取到n,m
int ea = a + ix[i], eb = b + iy[i]; //与访问点相连的边
int d = distance + (g[ea][eb] != ch[i]);
if(dist[pa][pb] > d)
{
dist[pa][pb] = d;
if(g[ea][eb] == ch[i]) q.push_front({pa, pb});
else q.push_back({pa, pb});
}
}
}
return -1;//一定不会取到
}
int main()
{
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 & 1) puts("NO SOLUTION");
else printf("%d\n", bfs());
}
return 0;
}