题目描述
在一个 2N×2N2N \times 2N2N×2N 的棋盘上(3≤N≤203 \le N \le 203≤N≤20),有一只“动态骑士”。它可以执行三种类型的移动:
- K\texttt{K}K 型:与常规国际象棋骑士相同,即沿水平或垂直方向移动 222 格,再沿垂直方向移动 111 格,共有 888 个可能的方向。
- B\texttt{B}B 型:沿对角线方向移动 222 格,共有 444 个可能的方向。
- T\texttt{T}T 型:沿棋盘的水平中轴或垂直中轴进行镜像反射,即坐标 (x,y)(x, y)(x,y) 变为 (2N+1−x,y)(2N+1-x, y)(2N+1−x,y) 或 (x,2N+1−y)(x, 2N+1-y)(x,2N+1−y)。
骑士不能走出棋盘,也不能落在障碍物上。此外,骑士不能连续两次使用相同类型的移动(第一次移动可以任意类型)。给定起点和终点,求从起点到终点的最少移动次数。如果无法到达,则报告无解。
输入格式
输入包含多组测试用例。每个测试用例的第一行为一个整数 NNN(N>0N > 0N>0),棋盘大小为 2N×2N2N \times 2N2N×2N。第二行为起点坐标 (xs,ys)(x_s, y_s)(xs,ys),第三行为终点坐标 (xt,yt)(x_t, y_t)(xt,yt)。随后若干行给出障碍物的坐标,每行两个整数,以 0 0 结束障碍物描述。输入以单独的 N=0N = 0N=0 结束。
输出格式
对于每个测试用例:
- 若起点与终点相同,输出
Result : 0。 - 若终点是障碍物,输出
Solution doesn't exist。 - 否则,输出最少移动次数,格式为
Result : X,其中 XXX 为步数;若不可达,则输出Solution doesn't exist。
样例
输入
3
1 1
1 1
2 2
0 0
10
1 1
20 20
20 1
1 20
3 1
3 2
3 3
2 3
1 3
0 0
10
2 1
18 12
2 2
5 6
7 2
8 3
9 4
1 15
7 12
8 13
9 11
11 9
12 4
11 3
9 5
2 7
3 8
6 5
0 0
3
1 1
5 4
2 2
2 4
2 5
3 1
3 2
3 3
3 6
4 2
4 3
4 5
5 1
5 3
5 5
6 1
6 4
6 5
6 6
0 0
输出
Result : 0
Solution doesn't exist
Result : 6
Result : 4
题目分析
本题要求在带障碍物的棋盘上求最短路径,但移动规则比常规骑士复杂,且包含三种不同类型的移动,并限制不能连续使用同类型。由于移动类型只有三种,且每种类型的移动数量有限(K\texttt{K}K 型 888 种,B\texttt{B}B 型 444 种,T\texttt{T}T 型 222 种),棋盘最大尺寸为 40×4040 \times 4040×40,状态总数可控。
最短路问题可以用广度优先搜索(BFS\texttt{BFS}BFS)求解,但状态不仅包含坐标 (x,y)(x, y)(x,y),还需要记录上一步使用的移动类型,因为规则限制了连续相同类型。因此每个位置对应 444 个状态(上一步为无、K\texttt{K}K、B\texttt{B}B、T\texttt{T}T),分别标记访问情况。BFS\texttt{BFS}BFS 从起点开始,每次扩展所有合法移动,并确保新移动的类型与上一步不同。当扩展到终点时,当前步数即为最少步数。
解题思路
状态表示与访问标记
定义结构体 movement 包含坐标 x、y、当前步数 steps 以及上一步移动类型 lastMoveType(000 表示起点,无上一步;111 表示 K\texttt{K}K;222 表示 B\texttt{B}B;333 表示 T\texttt{T}T)。使用三维数组 visited[x][y][type] 记录是否已访问过该状态,其中 type 对应上一步的类型(000 到 333)。若 visited[x][y][type] 为真,则不再入队,因为 BFS\texttt{BFS}BFS 首次访问即最优。
移动生成
- K\texttt{K}K 型:枚举 888 个偏移量 (Δx,Δy)∈{(−2,−1),(−1,−2),(1,−2),(2,−1),(2,1),(1,2),(−1,2),(−2,1)}(\Delta x, \Delta y) \in \{(-2,-1),(-1,-2),(1,-2),(2,-1),(2,1),(1,2),(-1,2),(-2,1)\}(Δx,Δy)∈{(−2,−1),(−1,−2),(1,−2),(2,−1),(2,1),(1,2),(−1,2),(−2,1)},检查目标坐标是否在棋盘内且非障碍物。
- B\texttt{B}B 型:枚举 444 个偏移量 (Δx,Δy)∈{(−2,−2),(2,−2),(2,2),(−2,2)}(\Delta x, \Delta y) \in \{(-2,-2),(2,-2),(2,2),(-2,2)\}(Δx,Δy)∈{(−2,−2),(2,−2),(2,2),(−2,2)},同样检查合法性。
- T\texttt{T}T 型:计算镜像坐标 (2N+1−x,y)(2N+1-x, y)(2N+1−x,y) 和 (x,2N+1−y)(x, 2N+1-y)(x,2N+1−y),若目标非障碍物则入队。
搜索流程
- 读入 NNN,若 N=0N=0N=0 则结束。
- 读入起点、终点,并读入障碍物坐标直到
0 0。 - 若起点与终点重合,输出
Result : 0并继续下一组。 - 若终点是障碍物,输出
Solution doesn't exist并继续。 - 初始化队列,将起点状态(
lastMoveType = 0,步数为 000)入队,并标记visited[startx][starty][0] = 1。 - 执行 BFS\texttt{BFS}BFS:
- 取出队首状态。
- 若坐标等于终点,输出当前步数并标记找到。
- 否则,对于三种移动类型(若当前上一步类型不等于该类型),尝试所有该类型的合法移动,若目标状态未访问,则入队并标记。
- 若队列空仍未找到,输出
Solution doesn't exist。
边界与剪枝
- 棋盘大小最大 404040,状态数为 40×40×4=640040 \times 40 \times 4 = 640040×40×4=6400,非常小。
- 注意
T型移动可能回到自身(当 x=N+0.5x = N+0.5x=N+0.5 时?但 2N2N2N 为偶数,镜像坐标不可能与自身重合,因为 (2N+1−x)=x(2N+1-x) = x(2N+1−x)=x 得到 x=N+0.5x = N+0.5x=N+0.5,不是整数,所以不会自环)。 - 障碍物只禁止停留,不禁止经过(移动是跳跃式的,不经过中间格)。
复杂度分析
- 每个状态最多扩展一次,状态数为 O((2N)2×4)=O(N2)O((2N)^2 \times 4) = O(N^2)O((2N)2×4)=O(N2),N≤20N \le 20N≤20,故状态数不超过 640064006400。
- 每次扩展尝试最多 8+4+2=148+4+2 = 148+4+2=14 种移动,总扩展次数 O(N2×14)O(N^2 \times 14)O(N2×14),非常小。
- 空间复杂度 O(N2)O(N^2)O(N2)。
代码实现
// A Chess Knight
// UVa ID: 633
// Verdict: Accepted
// Submission Date: 2017-06-02
// UVa Run Time: 0.000s
//
// 版权所有(C)2017,邱秋。metaphysis # yeah dot net
#include <bits/stdc++.h>
using namespace std;
struct movement {
int x, y, steps, lastMoveType;
};
int main() {
cin.tie(0), cout.tie(0), ios::sync_with_stdio(false);
int obstacle[64][64], visited[64][64][4], n;
int offsetK[8][2] = {
{-2, -1}, {-1, -2}, {1, -2}, {2, -1},
{2, 1}, {1, 2}, {-1, 2}, {-2, 1}
};
int offsetB[4][2] = {
{-2, -2}, {2, -2}, {2, 2}, {-2, 2}
};
while (cin >> n, n > 0) {
n *= 2;
memset(obstacle, 0, sizeof(obstacle));
memset(visited, 0, sizeof(visited));
int startx, starty, endx, endy;
cin >> startx >> starty >> endx >> endy;
int obstaclex, obstacley;
while (cin >> obstaclex >> obstacley) {
if (obstaclex == 0 && obstacley == 0) break;
obstacle[obstaclex][obstacley] = 1;
}
if (startx == endx && starty == endy) {
cout << "Result : 0\n";
continue;
}
if (obstacle[endx][endy]) {
cout << "Solution doesn't exist\n";
continue;
}
queue<movement> unvisited;
unvisited.push(movement{startx, starty, 0, 0});
bool found = false;
while (!unvisited.empty()) {
movement m = unvisited.front();
unvisited.pop();
if (m.x == endx && m.y == endy) {
cout << "Result : " << m.steps << '\n';
found = true;
break;
}
if (m.lastMoveType != 1 && !visited[m.x][m.y][1]) {
for (int i = 0; i < 8; i++) {
int nextx = m.x + offsetK[i][0], nexty = m.y + offsetK[i][1];
if (nextx < 1 || nextx > n || nexty < 1 || nexty > n) continue;
if (obstacle[nextx][nexty]) continue;
unvisited.push(movement{nextx, nexty, m.steps + 1, 1});
}
visited[m.x][m.y][1] = 1;
}
if (m.lastMoveType != 2 && !visited[m.x][m.y][2]) {
for (int i = 0; i < 4; i++) {
int nextx = m.x + offsetB[i][0], nexty = m.y + offsetB[i][1];
if (nextx < 1 || nextx > n || nexty < 1 || nexty > n) continue;
if (obstacle[nextx][nexty]) continue;
unvisited.push(movement{nextx, nexty, m.steps + 1, 2});
}
visited[m.x][m.y][2] = 1;
}
if (m.lastMoveType != 3 && !visited[m.x][m.y][3]) {
int nextx = n - m.x + 1, nexty = m.y;
if (!obstacle[nextx][nexty])
unvisited.push(movement{nextx, nexty, m.steps + 1, 3});
nextx = m.x, nexty = n - m.y + 1;
if (!obstacle[nextx][nexty])
unvisited.push(movement{nextx, nexty, m.steps + 1, 3});
visited[m.x][m.y][3] = 1;
}
}
if (!found) cout << "Solution doesn't exist\n";
}
return 0;
}
总结
本题通过引入移动类型和不能连续使用同类型的限制,将普通的最短路径问题扩展为带状态的最短路问题。利用 BFS\texttt{BFS}BFS 在状态空间(坐标 + 上一步类型)上进行搜索,能够保证首次到达终点时即为最少步数。关键点包括:
- 状态设计必须包含上一步移动类型,以正确应用限制规则。
- 特殊处理起点与终点重合以及终点为障碍物的情况。
- 移动生成时注意边界检查和障碍物检查。
- 由于棋盘较小,状态总数有限,BFS\texttt{BFS}BFS 效率很高。
此解法清晰直观,体现了在复杂移动规则下使用 BFS\texttt{BFS}BFS 求解最短路径的通用思路。
234

被折叠的 条评论
为什么被折叠?



