[语言月赛 202508] 迷宫寻路
每个格子指向唯一下一格的函数图,用三色标记 DFS 记忆化判环,q 次询问 O(1) 回答。
OJ: luogu
题目 ID: B4386
难度:入门
标签:记忆化搜索DFS函数图判环
日期: 2026-08-05 11:35
题意
数据范围:
思路
最直接的想法是每个询问一步步模拟,重复访问某个格子就说明进入了环:
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:10
* update_at: 2026-08-05 11:10
*/
// brute.cpp:小数据暴力解,对每个询问直接一步步模拟移动,
// 用 vis 时间戳判断是否进入环(重复访问同一格子)。
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 105;
int n, m, q;
int a[MAXN][MAXN];
int vis[MAXN][MAXN]; // 本次模拟中是否访问过
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> m >> q;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
cin >> a[i][j];
for (int t = 0; t < q; t++) {
int x, y;
cin >> x >> y;
memset(vis, 0, sizeof(vis));
int step = 0;
while (true) {
if (x < 1 || x > n || y < 1 || y > m) { // 离开迷宫
cout << step << '\n';
break;
}
if (vis[x][y]) { // 重复访问:进入环,永远出不去
cout << -1 << '\n';
break;
}
vis[x][y] = 1;
if (a[x][y] == 1) x--;
else if (a[x][y] == 2) x++;
else if (a[x][y] == 3) y--;
else y++;
step++;
}
}
return 0;
}这个暴力正确,但每个询问最多走
关键观察:每个格子只有一个确定的去向。整个迷宫是一张每个节点出度恰好为 1 的有向图(函数图):
- 从任意起点出发,路线唯一:要么走出迷宫,要么进入一个环;
- 同一个格子出发的答案固定,可以记忆化,无需重复计算。
用三色标记 DFS(
text
当前格子 (x,y)
├─ 下一格出界 → 0 步(已经离开)
├─ 下一格在栈中 → -1(成环,永远出不去)
└─ 下一格已算出 → 直接复用,+1 步以样例第 1 组(起点
代码
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:10
* update_at: 2026-08-05 11:10
*/
// 记忆化搜索:从每个格子出发只有一条固定路线(出度为 1 的函数图),
// 用三色标记 DFS 记录每个格子的答案,遇到栈中节点即进入环。
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 105;
int n, m, q;
int a[MAXN][MAXN]; // 1 上 2 下 3 左 4 右
int f[MAXN][MAXN]; // 0 未访问,-2 在递归栈中,-1 走不出,>0 步数
// 返回从 (x, y) 出发离开迷宫需要的步数;永远走不出返回 -1
int dfs(int x, int y) {
if (x < 1 || x > n || y < 1 || y > m) return 0; // 已经离开迷宫
if (f[x][y] == -2) return -1; // 回到栈中节点:成环
if (f[x][y] != 0) return f[x][y]; // 已算出答案(含 -1)
f[x][y] = -2; // 标记当前节点在递归栈中
int nx = x, ny = y;
if (a[x][y] == 1) nx--;
else if (a[x][y] == 2) nx++;
else if (a[x][y] == 3) ny--;
else ny++;
int res = dfs(nx, ny);
f[x][y] = (res == -1) ? -1 : res + 1;
return f[x][y];
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> m >> q;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
cin >> a[i][j];
for (int t = 0; t < q; t++) {
int x, y;
cin >> x >> y;
cout << dfs(x, y) << '\n';
}
return 0;
}复杂度
每个格子至多被 DFS 访问一次,时间
总结
"每个节点只有一个去向"是函数图的关键特征。遇到这类问题:
- 路线唯一,可记忆化;
- 三色标记(未访问 / 栈中 / 已完成)可以一次 DFS 同时完成判环和计数。
图示解析
text
数字迷宫(样例) 函数图视角
1 2 3 4 每个格子只有一条出边
4 3 2 1
2 3 4 1
从 (2,3)=2 向下: (2,3)→(3,3)→(3,4)→(2,4)→(1,4)→出界 = 5 步
从 (1,3)=3 向左: (1,3)→(1,2)→(2,2)→(2,1)→(2,2) 进入环 → -1读图方法:沿着箭头走,每格只有一个下一步。走出边界计步数;绕回已走过的格子就永远出不去。