「一本通 1.4 例 3」Knight Moves

GitHub跳转原题关系图返回列表

将棋盘格子建为无权图,利用 BFS 逐层扩展求骑士从起点到终点的最少移动步数。

OJ: roj

题目 ID: 1450

难度:入门

标签:广度优先搜索图论最短路

创建: 2026-09-30 11:11

更新: 2026-10-05 23:56

题目描述

计算国际象棋中骑士(马)从棋盘上一个格子跳到另一个格子所需的最少步数,每步可走 8 个「日」字方向。

输入:第一行是骑士数量 nn;每个骑士占 3 行:第一行是棋盘大小 LL(棋盘为 L×LL \times L,坐标 [0,L−1][0, L-1]),后两行是起点 (x1,y1)(x_1, y_1) 和终点 (x2,y2)(x_2, y_2)。

输出:对每个骑士输出一行最少步数;起点与终点相同则输出 0。例如输入棋盘大小 8、起点 0 0、终点 7 0,输出 5;起点 1 1、终点 1 1 则输出 0。

数据范围:4≤L≤3004 \le L \le 300,保证 0≤x,y≤L−10 \le x, y \le L-1。

思路

把每个格子看成无权图中的节点,骑士的 8 个马步位移就是边,从起点 BFS 逐层扩展。dist[x][y] 记录到该格子的最少步数,每步代价为 1,所以首次扩展到终点时的距离就是答案;起点等于终点时直接输出 0。复杂度为每次询问 O(L2)O(L^2)。

参考代码

Python 版:

python
#!/usr/bin/env python3
# Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
# rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
# rainboy的学习导航网站: https://idx.roj.ac.cn
# create_at: 2026-09-30 11:11
# update_at: 2026-09-30 11:11

import sys
from collections import deque
from functools import cache

KNIGHT_DELTAS = ((1, 2), (2, 1), (2, -1), (1, -2), (-1, -2), (-2, -1), (-2, 1), (-1, 2))  # 马步 8 个方向


@cache
def knight_graph(L: int) -> tuple[tuple[int, ...], ...]:
    """L×L 棋盘的马步邻接表:格子按 (x, y) → x*L+y 展平编号。

    同一组询问共享同一个 L,邻接表只建一次,之后每次 BFS 直接走表。
    """
    return tuple(
        tuple(
            (x + dx) * L + y + dy
            for dx, dy in KNIGHT_DELTAS
            if 0 <= x + dx < L and 0 <= y + dy < L  # 落在棋盘外的不建边
        )
        for x in range(L)
        for y in range(L)
    )


def knight_steps(L: int, start: int, target: int) -> int:
    """展平下标 start 到 target 的最少马步数;逐层 BFS,首次碰到 target 即最短。"""
    if start == target:
        return 0
    dist = [-1] * (L * L)  # -1 = 还没访问过
    dist[start] = 0
    queue = deque([start])
    while queue:
        pos = queue.popleft()
        step = dist[pos] + 1
        for nxt in knight_graph(L)[pos]:
            if dist[nxt] < 0:
                if nxt == target:  # BFS 按层扩展,第一次到达就是最少步数
                    return step
                dist[nxt] = step
                queue.append(nxt)
    return -1  # L≥4 的完整棋盘马步图连通,不会走到这里


def solve() -> None:
    data = iter(map(int, sys.stdin.buffer.read().split()))
    n = next(data)  # 骑士数量
    out = []
    for _ in range(n):
        L = next(data)  # 棋盘边长
        sx, sy, tx, ty = next(data), next(data), next(data), next(data)
        out.append(str(knight_steps(L, sx * L + sy, tx * L + ty)))
    print('\n'.join(out))


if __name__ == "__main__":
    solve()

C++ 版(同一算法):

cpp
/**
 * Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
 * rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
 * rainboy的学习导航网站: https://idx.roj.ac.cn
 * create_at: 2026-10-05 23:55
 * update_at: 2026-10-05 23:55
 */
#include <cstdio>
#include <queue>
#include <cstring>
using namespace std;

typedef long long ll;

const int MAXL = 305;

int L;                          // 棋盘大小 L×L
int dist[MAXL][MAXL];           // dist[x][y] 表示起点到 (x,y) 的最少步数,-1 表示未访问
int sx, sy, tx, ty;             // 起点与终点坐标

// 骑士的 8 个马步位移
const int fx[8] = {1, 1, -1, -1, 2, 2, -2, -2};
const int fy[8] = {2, -2, 2, -2, 1, -1, 1, -1};

// BFS 求起点到终点的最少马步数
int bfs() {
    if (sx == tx && sy == ty)
        return 0;               // 起点即终点,0 步

    queue< pair<int,int> > q;
    memset(dist, -1, sizeof(dist));
    dist[sx][sy] = 0;
    q.push(make_pair(sx, sy));

    while (!q.empty()) {
        pair<int,int> cur = q.front();
        q.pop();
        int x = cur.first, y = cur.second;

        // 枚举 8 个马步后继
        for (int i = 0; i < 8; ++i) {
            int nx = x + fx[i];
            int ny = y + fy[i];
            if (nx < 0 || nx >= L || ny < 0 || ny >= L)
                continue;       // 越界跳过
            if (dist[nx][ny] != -1)
                continue;       // 已访问过
            dist[nx][ny] = dist[x][y] + 1;
            if (nx == tx && ny == ty)
                return dist[nx][ny]; // 首次到达终点即最短
            q.push(make_pair(nx, ny));
        }
    }
    return -1;                  // 题目保证连通,不会到这里
}

int main() {
    int T;
    scanf("%d", &T);
    while (T--) {
        scanf("%d", &L);
        scanf("%d %d", &sx, &sy);
        scanf("%d %d", &tx, &ty);
        printf("%d\n", bfs());
    }
    return 0;
}