马的遍历

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

把棋盘看成无权图,从起点做一次 BFS,就能同时求出马到所有格子的最短步数。

OJ: luogu

题目 ID: P1443

难度:普及-

标签:bfs最短路图论模板题python

日期: 2026-06-19 08:03

题意

给一个 n × m 的棋盘,起点是 (x, y),现在有一匹马放在这个位置上。

要求输出一个表格,表示马从起点到棋盘上每个格子的最少步数。

如果某个格子永远到不了,就输出 -1

样例中的最短步数分布可以写成这样:

1 2 3
1 0 3 2
2 3 -1 1
3 2 1 4

思路

最直接的办法,是把每个格子都当成一个独立终点,然后从起点单独跑一次 BFS 去求它的答案。

这个版本最贴近“逐个询问”的思路:

cpp
#include <bits/stdc++.h>
using namespace std;

const int MAXN = 35;

int n, m, sx, sy;
int ans[MAXN][MAXN];
int dx[8] = {-2, -2, -1, -1, 1, 1, 2, 2};
int dy[8] = {-1, 1, -2, 2, -2, 2, -1, 1};

struct Node {
    int x;
    int y;
};

bool in_board(int x, int y) {
    return x >= 1 && x <= n && y >= 1 && y <= m;
}

int single_bfs(int tx, int ty) {
    static int dista[MAXN][MAXN];
    memset(dista, -1, sizeof(dista));

    queue<Node> q;
    dista[sx][sy] = 0;
    q.push((Node){sx, sy});

    while (!q.empty()) {
        Node u = q.front();
        q.pop();

        if (u.x == tx && u.y == ty) {
            return dista[tx][ty];
        }

        for (int i = 0; i < 8; i++) {
            int nx = u.x + dx[i];
            int ny = u.y + dy[i];

            if (!in_board(nx, ny)) {
                continue;
            }
            if (dista[nx][ny] != -1) {
                continue;
            }

            dista[nx][ny] = dista[u.x][u.y] + 1;
            q.push((Node){nx, ny});
        }
    }

    return -1;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    cin >> n >> m >> sx >> sy;

    // 朴素做法:对每个格子都单独跑一次 BFS。
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            ans[i][j] = single_bfs(i, j);
        }
    }

    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            if (j > 1) {
                cout << ' ';
            }
            cout << ans[i][j];
        }
        cout << '\n';
    }

    return 0;
}

但这样会重复做很多次搜索。

把棋盘看成无权图

我们把每个格子看成图上的一个点。

如果马能从一个格子一步跳到另一个格子,就在这两个点之间连一条边。由于每次跳跃都只算 1 步,所以所有边权都相同。

这样一来,这题就变成了一个标准的:

无权图单源最短路问题。

为什么 BFS 正好合适

在无权图中,BFS 是按层扩展的:

  • 先访问距离起点 0 步的点;
  • 再访问距离起点 1 步的点;
  • 再访问距离起点 2 步的点。

因此某个格子第一次被访问到时,记录的距离就一定是最短步数。

这正好满足题目要求。

正式做法

  1. 先把所有格子的答案初始化成 -1
  2. 把起点距离设为 0,压入队列;
  3. 每次取出一个格子,尝试向 8 个马步方向跳;
  4. 如果新格子没有访问过,就把它的距离设成当前距离加 1,然后入队。

整张图只需要做一次 BFS,就能同时得到所有格子的答案。

Python 知识

  • deque.popleft()O(1)O(1) 队首弹出,对应 C++ 的 queue.front()+pop()
  • distance = [[-1] * m for _ in range(n)] 每次创建独立行,避免二维列表浅拷贝。
  • 队列元素直接使用 (x,y) 元组,不需要定义结构体。
  • "\n".join(" ".join(map(str,row)) ...) 一次构造多行输出,减少大量 print 调用。
  • /home/rainboy/mycode/hugo-blog/content/program_language/python/bfs_shortest.md:隐式图 BFS 与距离记录。
  • /home/rainboy/mycode/hugo-blog/content/program_language/python/cpp_to_python_pitfalls.mddeque 和二维列表初始化。

代码

python
from collections import deque


n, m, start_x, start_y = map(int, input().split())
distance = [[-1] * m for _ in range(n)]
distance[start_x - 1][start_y - 1] = 0
queue = deque([(start_x - 1, start_y - 1)])
moves = ((-2, -1), (-2, 1), (-1, -2), (-1, 2),
         (1, -2), (1, 2), (2, -1), (2, 1))

while queue:
    x, y = queue.popleft()
    for dx, dy in moves:
        nxt_x, nxt_y = x + dx, y + dy
        if 0 <= nxt_x < n and 0 <= nxt_y < m and distance[nxt_x][nxt_y] == -1:
            distance[nxt_x][nxt_y] = distance[x][y] + 1
            queue.append((nxt_x, nxt_y))

print("\n".join(" ".join(map(str, row)) for row in distance))
cpp
#include <bits/stdc++.h>
using namespace std;

const int MAXN = 405;

int n, m, sx, sy;
int dista[MAXN][MAXN];
int dx[8] = {-2, -2, -1, -1, 1, 1, 2, 2};
int dy[8] = {-1, 1, -2, 2, -2, 2, -1, 1};

struct Node {
    int x;
    int y;
};

bool in_board(int x, int y) {
    return x >= 1 && x <= n && y >= 1 && y <= m;
}

void bfs() {
    queue<Node> q;
    dista[sx][sy] = 0;
    q.push((Node){sx, sy});

    while (!q.empty()) {
        Node u = q.front();
        q.pop();

        for (int i = 0; i < 8; i++) {
            int nx = u.x + dx[i];
            int ny = u.y + dy[i];

            if (!in_board(nx, ny)) {
                continue;
            }
            if (dista[nx][ny] != -1) {
                continue;
            }

            dista[nx][ny] = dista[u.x][u.y] + 1;
            q.push((Node){nx, ny});
        }
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    cin >> n >> m >> sx >> sy;

    memset(dista, -1, sizeof(dista));
    bfs();

    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            if (j > 1) {
                cout << ' ';
            }
            cout << dista[i][j];
        }
        cout << '\n';
    }

    return 0;
}

复杂度

  • 时间复杂度:O(nm)O(nm)
  • 空间复杂度:O(nm)O(nm)

总结

这题的关键是识别出“棋盘上每次走一步的最少步数”本质上就是无权图最短路。

一旦转成图模型,单源 BFS 就是最自然、也最标准的做法。

一图流解析

这张图把本题的建模、关键转移、实现检查和训练方法压缩到一页,适合读完正文后复盘。

一图流解析