DFS 回溯枚举所有简单路径,按 上左下右 方向序输出全部路线,无路输出 -1。
OJ: luogu
题目 ID: P1238
难度:普及/提高-
标签:DFS回溯网格
日期: 2026-08-05 11:35
题意
- 路径中不能重复经过同一个点;
- 只能上下左右四个方向;
- 优先顺序:上、左、右、下;
- 每条路径一行,用
(x,y)->(x,y)->...输出;无路输出。
数据范围:
思路
最直接的想法是枚举所有路径:
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-08-05 11:20
* update_at: 2026-08-05 11:20
*/
// brute.cpp:小数据暴力解,用递归回溯枚举所有简单路径,
// 与 main.cpp 独立实现,方向顺序保持一致,用于交叉验证。
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 20;
int m, n;
int mp[MAXN][MAXN];
int sx, sy, ex, ey;
bool vis[MAXN][MAXN];
vector<pair<int, int>> path;
bool has_ans;
int dx[4] = {-1, 0, 0, 1};
int dy[4] = {0, -1, 1, 0};
void print_path() {
for (size_t i = 0; i < path.size(); i++) {
if (i) cout << "->";
cout << "(" << path[i].first << "," << path[i].second << ")";
}
cout << '\n';
}
void dfs(int x, int y) {
if (x == ex && y == ey) {
has_ans = true;
print_path();
return;
}
for (int k = 0; k < 4; k++) {
int nx = x + dx[k], ny = y + dy[k];
if (nx < 1 || nx > m || ny < 1 || ny > n) continue;
if (mp[nx][ny] == 0 || vis[nx][ny]) continue;
vis[nx][ny] = true;
path.push_back({nx, ny});
dfs(nx, ny);
path.pop_back();
vis[nx][ny] = false;
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> m >> n;
for (int i = 1; i <= m; i++)
for (int j = 1; j <= n; j++)
cin >> mp[i][j];
cin >> sx >> sy >> ex >> ey;
vis[sx][sy] = true;
path.push_back({sx, sy});
dfs(sx, sy);
if (!has_ans) cout << -1 << '\n';
return 0;
}关键点:题目要输出所有路径,所以不能用 BFS(BFS 只找一条最短路径),必须用 DFS 回溯——每到达终点就输出当前路径,然后返回继续尝试其他分支。
DFS 结构:
text
dfs(x, y):当前在 (x, y)
├─ 到达终点 → 输出 path_x[0..path_len-1]
└─ 按 上、左、右、下 顺序尝试四个方向
├─ 出界 / 是墙 / 已访问 → 跳过
└─ 标记访问 → 记录路径 → dfs(nx, ny) → 回溯撤销方向顺序为什么重要:题目要求按"上左下右"优先顺序输出路径。方向数组 dx = {-1, 0, 0, 1}, dy = {0, -1, 1, 0} 严格对应这个顺序,DFS 按这个顺序尝试,输出自然就是题目要求的顺序。
以样例第一条路径为例,从
text
(1,1)->(2,1)->(2,2)->...->(5,6)代码
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-08-05 11:20
* update_at: 2026-08-29 16:59
*/
// DFS 枚举所有简单路径:路径上不能重复经过点,方向按 左、上、右、下 顺序尝试。
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 20;
int m, n; // m 行 n 列
int mp[MAXN][MAXN]; // 1 可走,0 不可走
int sx, sy, ex, ey; // 起点、终点
bool vis[MAXN][MAXN]; // 当前路径上是否已走过
int path_x[MAXN * MAXN], path_y[MAXN * MAXN]; // 路径上的点
int path_len; // 当前路径长度
bool has_ans; // 是否至少找到一条路径
int dx[4] = {0, -1, 0, 1}; // 左 上 右 下
int dy[4] = {-1, 0, 1, 0};
void print_path() {
for (int i = 0; i < path_len; i++) {
if (i) cout << "->";
cout << "(" << path_x[i] << "," << path_y[i] << ")";
}
cout << '\n';
}
// 当前在 (x, y),尝试走到终点
void dfs(int x, int y) {
if (x == ex && y == ey) { // 到达终点,输出当前完整路径
has_ans = true;
print_path();
return;
}
for (int k = 0; k < 4; k++) { // 按 左上右下 顺序尝试
int nx = x + dx[k], ny = y + dy[k];
if (nx < 1 || nx > m || ny < 1 || ny > n) continue; // 出界
if (mp[nx][ny] == 0) continue; // 不可走
if (vis[nx][ny]) continue; // 路径不能重复
vis[nx][ny] = true;
path_x[path_len] = nx;
path_y[path_len] = ny;
path_len++;
dfs(nx, ny);
path_len--; // 回溯:撤销这一步
vis[nx][ny] = false;
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> m >> n;
for (int i = 1; i <= m; i++)
for (int j = 1; j <= n; j++)
cin >> mp[i][j];
cin >> sx >> sy >> ex >> ey;
if (mp[sx][sy] == 0 || mp[ex][ey] == 0) {
cout << -1 << '\n';
return 0;
}
vis[sx][sy] = true;
path_x[0] = sx;
path_y[0] = sy;
path_len = 1;
dfs(sx, sy);
if (!has_ans) cout << -1 << '\n';
return 0;
}复杂度
总结
- 输出所有方案 → DFS 回溯;
- 输出最短方案 → BFS;
- 路径不重复点 →
vis标记,回溯时撤销; - 方向顺序敏感的输出 → 方向数组严格按题意排列。
图示解析
text
(1,1)→(2,1)→(2,2)→(2,3)→(2,4)→(2,5)→(3,5)→(3,4)→(3,3)
│
(5,6)←(5,5)←(4,5)←(4,4)←(4,3)←──────────────────────┘读图方法:这是样例第一条路径(上左下右顺序下的第一条)。注意路径会绕行(如