≤5×5 棋盘上从给定起点出发、马走日不重复的全遍历路径条数,回溯按邻接表枚举合法马步,位图维护已访问集合。
OJ: roj
题目 ID: 1219
难度:普及-
标签:搜索回溯位运算cpp
创建: 2026-09-30 00:15
更新: 2026-10-05 05:50
题目描述
给定
样例:输入 5 4 0 0 输出 32。
思路
参考代码
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 00:26
# update_at: 2026-09-30 00:26
import sys
from functools import cache
MOVES = ((1, 2), (1, -2), (-1, 2), (-1, -2), (2, 1), (2, -1), (-2, 1), (-2, -1)) # 马走日的 8 个日字方向
@cache
def neighbors(n: int, m: int) -> tuple[tuple[int, ...], ...]:
"""nbrs[pos]:n×m 棋盘上马从编号 pos 的格子一步可达的格子编号表(越界已滤掉)。
起点不同但棋盘相同时多组数据可复用同一张表,所以按 (n, m) 做缓存。
"""
return tuple(
tuple(
(r + dr) * m + (c + dc)
for dr, dc in MOVES
if 0 <= r + dr < n and 0 <= c + dc < m
)
for r in range(n) for c in range(m)
)
def count_tours(n: int, m: int, x: int, y: int) -> int:
"""统计从 (x, y) 出发、马走日且不重访任何格子的棋盘全遍历路径条数。
棋盘压成位图:格子 (r, c) 编号为 r*m+c,visited 第 pos 位为 1 表示已访问。
"""
nbrs = neighbors(n, m)
total = n * m
def dfs(pos: int, visited: int, left: int) -> int:
"""从 pos 出发、还有 left 个格子未访问时的完整遍历条数。"""
if left == 0:
return 1 # 所有格子恰好各走一次
cnt = 0
for nxt in nbrs[pos]:
free = not visited >> nxt & 1 # 移位后取位:目标格还没走过
if free:
cnt += dfs(nxt, visited | 1 << nxt, left - 1)
return cnt
start = x * m + y
return dfs(start, 1 << start, total - 1)
def solve() -> None:
data = iter(map(int, sys.stdin.buffer.read().split()))
out: list[str] = []
T = next(data)
for _ in range(T):
n, m, x, y = next(data), next(data), next(data), next(data)
out.append(str(count_tours(n, m, x, y)))
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 05:50
* update_at: 2026-10-05 05:50
*/
#include <iostream>
#include <vector>
using namespace std;
typedef long long ll;
// 马走日的 8 个日字方向:(dr, dc) 表示行列偏移
const int DR[8] = { 1, 1, -1, -1, 2, 2, -2, -2 };
const int DC[8] = { 2, -2, 2, -2, 1, -1, 1, -1 };
// n <= 5, m <= 5
const int MAXN = 6;
const int MAXM = 6;
const int MAXK = 8;
// nbrs[r][c][k] 表示从 (r,c) 出发的第 k 个合法落点编号(编号 = r*m+c);k_cnt[r][c] 是合法落点数
int nbrs[MAXN][MAXM][MAXK];
int k_cnt[MAXN][MAXM];
int n, m;
int total_cells; // n*m
int visited; // 已访问位图:第 pos 位为 1 表示该格子已访问
ll ans; // 当前棋盘 + 起点下的全遍历路径条数
// dfs(pos, left):从 pos 出发、还有 left 个格子未访问时的完整遍历条数。
// visited 进入时已置位 pos,退出前清位实现手工回溯。
void dfs(int pos, int left) {
if (left == 0) {
ans++; // 所有格子恰好各走一次,计一条完整路径
return;
}
int r = pos / m;
int c = pos % m;
for (int i = 0; i < k_cnt[r][c]; i++) {
int nxt = nbrs[r][c][i];
if ( (visited >> nxt & 1) == 0 ) { // 落点未访问过
visited |= 1 << nxt; // 进入:置位
dfs(nxt, left - 1);
visited &= ~(1 << nxt); // 回溯:清位
}
}
}
void build_neighbors() {
// 预建邻接表:nbrs[r][c] 列出 (r,c) 一步可达的合法落点编号
for (int r = 0; r < n; r++) {
for (int c = 0; c < m; c++) {
int cnt = 0;
for (int k = 0; k < 8; k++) {
int nr = r + DR[k];
int nc = c + DC[k];
if (0 <= nr && nr < n && 0 <= nc && nc < m) {
nbrs[r][c][cnt++] = nr * m + nc;
}
}
k_cnt[r][c] = cnt;
}
}
}
void solve_one(int x, int y) {
build_neighbors();
int start_pos = x * m + y;
visited = 1 << start_pos;
ans = 0;
dfs(start_pos, total_cells - 1);
cout << ans << "\n";
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
if ( !(cin >> T) ) return 0;
while (T--) {
int x, y;
cin >> n >> m >> x >> y;
total_cells = n * m;
solve_one(x, y);
}
return 0;
}