abc 246 e 题解
abc 246 e
题意
有一个 \(N \times N\) 的棋盘,\((i, j)\) 表示棋盘第 \(i\) 行第 \(j\) 列的格子,行编号和列编号都是 \(1, 2, \dots, N\)。
棋盘用 \(N\) 个长度为 \(N\) 的字符串 \(S_i\) 描述:
-
如果 $S_{i, j} = $
.
,则格子 \((i, j)\) 是空的。 -
否则,格子 \((i, j)\) 有障碍。
一开始格子 \((A_x, A_y)\) 放了一颗棋子。在每次移动中,棋子可以朝某个对角线(某个斜 45 度方向)行走若干步后停下。棋子不能走出棋盘范围,并且该次移动的路径中不能包含障碍格子。
请你求出棋子从 \((A_x, A_y)\) 走到 \((B_x, B_y)\) 所需的最少移动次数。如果无法移动到 \((B_x, B_y)\),输出 -1
。
\(2 \le N \le 1500\)。
\(1 \le A_x, A_y, B_x, B_y \le N\)。
\((A_x, A_y) \ne (B_x, B_y)\)。
思路
很明显,这题求最短路,是广搜。
但是,这个移动方式很让人头疼,每次到底挪动几次呢?
如果每次都去枚举移动的步数,时间复杂度又变成了 \(O(n ^ 3)\),会超时。
那么,我们应该怎么解决呢?
其实,每次只用走一步,但是,我们每次需要记录是往哪个方向走的。
如果当前走的方向和转移来的那个状态的转移方向是不同的,就需要多一次操作,否则,就还是这次操作。
因为每次转移有可能增加操作次数,有可能不增加,所以是 0-1 BFS,用双端队列维护。
#include <bits/stdc++.h>
using namespace std;
const int N = 1510;
const int dx[] = {1, 1, -1, -1};
const int dy[] = {1, -1, 1, -1};
int n, sx, sy, fx, fy, d[N][N][4];
char c[N][N];
struct Node {
int x, y, dir; // dir 表示方向
};
deque<Node> que;
void Record(int x, int y, int dis, int dir, int nowd) {
if (x < 1 || x > n || y < 1 || y > n || c[x][y] == '#' || d[x][y][nowd] <= dis + (nowd != dir)) {
return ;
}
d[x][y][nowd] = dis;
if (nowd == dir) {
que.push_front({x, y, nowd});
} else {
d[x][y][nowd]++;
que.push_back({x, y, nowd});
}
}
void bfs() {
for (Record(sx, sy, 0, 4, 4); !que.empty(); ) {
Node u = que.front();
que.pop_front();
for (int i = 0; i < 4; i++) { // 转移
Record(u.x + dx[i], u.y + dy[i], d[u.x][u.y][u.dir], u.dir, i);
}
}
}
int main() {
ios::sync_with_stdio(0), cin.tie(0);
cin >> n >> sx >> sy >> fx >> fy;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
cin >> c[i][j];
d[i][j][0] = d[i][j][1] = d[i][j][2] = d[i][j][3] = 1e9; // 初始化
}
}
bfs();
int ans = 1e9;
for (int i = 0; i < 4; i++) {
if (d[fx][fy][i] != 1e9) {
ans = min(ans, d[fx][fy][i]);
}
}
cout << (ans == 1e9 ? -1 : ans);
return 0;
}