起步方向和每个撞墙点的左/右转向构成决策树,回溯枚举所有走法并刷新走过的最大格数,撞上自己足迹即结束。
OJ: roj
题目 ID: 2079
难度:普及-
标签:搜索DFS回溯网格python
创建: 2026-10-01 07:22
更新: 2026-10-06 10:38
题目描述
N B,随后 E2,字母为列、数字为行);样例输入 8 4 / E2 / A6 / G1 / F5,输出 33。
思路
主动的选择只有起步方向(右或下)和每个撞墙点的左/右转向,于是把"起步方向 + 每次撞墙的转向"当成决策树回溯枚举:直行段按规则被动走到底,并沿途刷新走过的最大格数。撞上自己的足迹要立即结束、不再转弯;足迹属于单条分支,回溯时必须撤销。
参考代码
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-10-01 07:38
# update_at: 2026-10-01 07:38
import sys
DR = (-1, 0, 1, 0) # 北、东、南、西四个方向的行偏移
DC = (0, 1, 0, -1) # 北、东、南、西四个方向的列偏移
UNDO = 0 # 栈帧类型:回退帧,撤销一段直行的足迹并还原计数
WALK = 1 # 栈帧类型:直行帧,从 (row, col) 沿 dir 直行到底后再左右转弯
def solve() -> None:
data = iter(sys.stdin.read().split())
N = int(next(data))
B = int(next(data))
blocked = bytearray(N * N) # 路障占用表,下标 = 行 * N + 列
for _ in range(B):
pos = next(data)
col = ord(pos[0]) - 65 # 列字母 A1 的 A → 0
row = int(pos[1:]) - 1 # 行号 A1 的 1 → 0
blocked[row * N + col] = 1
trail = bytearray(N * N) # 当前路线已经走过的格子
trail[0] = 1 # 出发点 A1 已计入
count = 1
ans = 1
# 手写栈代替深递归:直行帧 (WALK, row, col, dir),回退帧 (UNDO, 本段足迹下标)
stack: list[tuple] = [(WALK, 0, 0, 1), (WALK, 0, 0, 2)] # 初始只能向东或向南
while stack:
frame = stack.pop()
if frame[0] == UNDO:
for idx in frame[1]:
trail[idx] = 0 # 回溯:撤掉这一段直行的足迹
count -= len(frame[1])
continue
_, row, col, d = frame
run: list[int] = []
hit_trail = False # 撞上自己的足迹:题目规定此时整段散步结束,不再转弯
# 选定方向后一直走,直到棋盘边缘、路障或自己的足迹
while True:
nr, nc = row + DR[d], col + DC[d]
if not 0 <= nr < N or not 0 <= nc < N:
break
idx = nr * N + nc
if trail[idx]:
hit_trail = True
break
if blocked[idx]:
break
trail[idx] = 1
run.append(idx)
row, col = nr, nc
count += 1
if count > ans:
ans = count
stack.append((UNDO, run)) # 子树全部结束后再统一撤销本段足迹
if hit_trail:
continue # 撞到自己的足迹,无路可走,直接回溯
# 只有撞边缘 / 路障才转弯:左转与右转都试,下一步走得通才入栈
for nd in ((d + 1) % 4, (d + 3) % 4):
nr, nc = row + DR[nd], col + DC[nd]
if 0 <= nr < N and 0 <= nc < N and not trail[nr * N + nc] and not blocked[nr * N + nc]:
stack.append((WALK, row, col, nd))
print(ans)
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-06 10:38
* update_at: 2026-10-06 10:38
*/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN = 130;
int N, B;
char blocked[MAXN][MAXN]; // blocked[row][col] = 1 表示该格是路障
char trail[MAXN][MAXN]; // 当前这条路线已经走过的格子
// 四个方向的行列偏移:北、东、南、西
int DR[4] = {-1, 0, 1, 0};
int DC[4] = {0, 1, 0, -1};
const int UNDO = 0; // 栈帧类型:回退帧,撤销一段直行留下的足迹
const int WALK = 1; // 栈帧类型:直行帧,从 (row, col) 沿 dir 直行到底
// 一行直行是一个栈帧;用显式栈代替深递归,避免路线太长时爆栈
struct Frame {
int type;
int row;
int col;
int dir;
int run_len; // 回退帧需要撤销的格子数
};
int count_cells; // 当前路线已经走过的格子数
int answer; // 目前为止能走过的最多格子数
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> N >> B;
// 路障坐标形如 E2:字母是列(A 起),数字是行(1 起)
for (int i = 0; i < B; i++) {
string pos;
cin >> pos;
int col = pos[0] - 'A';
int row = 0;
int len = pos.size();
for (int j = 1; j < len; j++) {
row = row * 10 + (pos[j] - '0');
}
blocked[row - 1][col] = 1;
}
trail[0][0] = 1;
count_cells = 1;
answer = 1;
vector<Frame> stk;
// 起步只能向右(东)或向下(南)
stk.push_back(Frame{WALK, 0, 0, 1, 0});
stk.push_back(Frame{WALK, 0, 0, 2, 0});
while (!stk.empty()) {
Frame f = stk.back();
stk.pop_back();
if (f.type == UNDO) {
// 撤销这一段直行的足迹:从起点沿原方向走 run_len 格逐格清空
int r = f.row;
int c = f.col;
for (int k = 0; k < f.run_len; k++) {
r += DR[f.dir];
c += DC[f.dir];
trail[r][c] = 0;
}
count_cells -= f.run_len;
continue;
}
int r = f.row;
int c = f.col;
int d = f.dir;
int run_len = 0;
int hit_trail = 0; // 撞上自己的足迹:题目规定此时整段散步结束,不再转弯
// 选定方向后一直走,直到棋盘边缘、路障或自己的足迹
while (true) {
int nr = r + DR[d];
int nc = c + DC[d];
if (nr < 0 || nr >= N || nc < 0 || nc >= N) break;
if (blocked[nr][nc]) break;
if (trail[nr][nc]) {
hit_trail = 1;
break;
}
trail[nr][nc] = 1;
run_len++;
r = nr;
c = nc;
}
count_cells += run_len;
if (count_cells > answer) answer = count_cells;
// 先压入回退帧,等这棵子树的兄弟分支都跑完,再统一撤销本段足迹
stk.push_back(Frame{UNDO, f.row, f.col, d, run_len});
if (hit_trail) continue; // 撞到自己的足迹,无路可走,直接回溯
// 只有撞边缘或路障才转弯:左转、右转都试,第一步走得通才入栈
int nd = (d + 1) % 4;
int nr = r + DR[nd];
int nc = c + DC[nd];
if (nr >= 0 && nr < N && nc >= 0 && nc < N && !blocked[nr][nc] && !trail[nr][nc]) {
stk.push_back(Frame{WALK, r, c, nd, 0});
}
nd = (d + 3) % 4;
nr = r + DR[nd];
nc = c + DC[nd];
if (nr >= 0 && nr < N && nc >= 0 && nc < N && !blocked[nr][nc] && !trail[nr][nc]) {
stk.push_back(Frame{WALK, r, c, nd, 0});
}
}
cout << answer << endl;
return 0;
}