Knight Moves

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

棋盘上马的最少步数就是隐式无权图的单源最短路:边权全为 1,按步数分层 BFS,首次到达终点即答案。

OJ: roj

题目 ID: 1257

难度:普及-

标签:搜索BFS网格队列python

创建: 2026-09-30 02:01

更新: 2026-10-07 12:15

题目描述

输入 TT 表示测试样例组数。每组给出棋盘边长 LL(4⩽L⩽3004 \leqslant L \leqslant 300)、马的起点与终点坐标(均在 0..L−10..L-1 内);马走「日」字,共 8 个方向。求从起点跳到终点的最少步数,起止相同输出 00。

样例:输入 3 / 8 / 0 0 / 7 0 / 100 / 0 0 / 30 50 / 10 / 1 1 / 1 1,输出 5 / 28 / 0。

思路

棋盘是隐式无权图,边权全为 1,用分层 BFS 求最短路:起点距离记 0,出队时把 8 个合法落点中未访问的记为「当前距离 +1」并入队,首次到达终点时的步数就是答案。入队即标记使每格至多扩展一次,复杂度 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 02:01
# update_at: 2026-10-14 12:00

import sys
from collections import deque

# 马走日的 8 个偏移:|dr| + |dc| == 3 且两分量都非零,恰好是 (±1, ±2) 与 (±2, ±1)
DIRS = [(dr, dc) for dr in (-2, -1, 1, 2) for dc in (-2, -1, 1, 2) if abs(dr) + abs(dc) == 3]  # 8 个马步


def min_knight_steps(size: int, start: tuple[int, int], target: tuple[int, int]) -> int:
    """BFS 求马从 start 到 target 的最少步数;起止相同返回 0。"""
    if start == target:
        return 0
    dist = {start: 0}                 # tuple 坐标直接当 key,既是距离表也是访问标记
    queue = deque([start])
    while queue:
        r, c = queue.popleft()
        step = dist[r, c] + 1         # 出队点的下一层步数
        for dr, dc in DIRS:
            nr, nc = r + dr, c + dc
            if 0 <= nr < size and 0 <= nc < size and (nr, nc) not in dist:
                if (nr, nc) == target:
                    return step       # BFS 首次到达即最短
                dist[nr, nc] = step
                queue.append((nr, nc))
    return -1                         # 棋盘够大时马必可达,此处仅兜底


def solve() -> None:
    data = iter(map(int, sys.stdin.buffer.read().split()))
    out: list[str] = []
    T = next(data)

    for _ in range(T):
        size = next(data)                                # 棋盘边长 L
        start = (next(data), next(data))                 # 起点 (x, y)
        target = (next(data), next(data))                # 终点 (x, y)
        out.append(str(min_knight_steps(size, start, target)))

    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 07:11
 * update_at: 2026-10-05 07:11
 */
// main.cpp:棋盘上马走日的最少步数,按步数分层 BFS 求隐式无权图最短路。
#include <cstdio>
#include <cstring>

typedef long long ll;

const int MAXL = 305;

ll L;                        // 当前样例的棋盘边长
ll sx, sy, tx, ty;           // 起点、终点的坐标

int dr[8] = {-2, -2, -1, -1, 1, 1, 2, 2};  // 马的 8 个走法
int dc[8] = {-1, 1, -2, 2, -2, 2, -1, 1};  // 分别对应 (±1,±2) 与 (±2,±1)

int dist[MAXL][MAXL];        // dist[x][y] = 起点到 (x,y) 的最少步数,-1 表示未访问
struct Node {
    ll x;
    ll y;
};
Node que[MAXL * MAXL];       // BFS 队列:每格入队即标记,至多入队一次

// 从起点 BFS 到终点,首次到达时的步数即最少步数。
ll bfs() {
    if (sx == tx && sy == ty) {
        return 0;
    }
    memset(dist, -1, sizeof(dist));
    ll head = 0;
    ll tail = 0;
    dist[sx][sy] = 0;
    que[tail].x = sx;
    que[tail].y = sy;
    tail++;
    while (head < tail) {
        ll x = que[head].x;
        ll y = que[head].y;
        ll step = dist[x][y] + 1;    // 出队格子的下一层步数
        head++;
        for (int k = 0; k < 8; k++) {
            ll nx = x + dr[k];
            ll ny = y + dc[k];
            if (nx < 0 || nx >= L || ny < 0 || ny >= L) continue;
            if (dist[nx][ny] != -1) continue;
            if (nx == tx && ny == ty) return step;  // BFS 首次到达即最短
            dist[nx][ny] = step;
            que[tail].x = nx;
            que[tail].y = ny;
            tail++;
        }
    }
    return -1;                       // 棋盘够大时马必可达,此处仅兜底
}

int main() {
    ll T;
    scanf("%lld", &T);
    for (ll i = 0; i < T; i++) {
        scanf("%lld", &L);
        scanf("%lld%lld", &sx, &sy);
        scanf("%lld%lld", &tx, &ty);
        printf("%lld\n", bfs());
    }
    return 0;
}