在模板范围内搜索,并记录每个模格子第一次对应的绝对坐标;若同一模格子被不同绝对坐标到达,就能无限走远。
OJ: luogu
题目 ID: P1363
难度:普及+/提高
标签:BFS图论网格周期python
日期: 2026-06-20 14:41
题意
给一个 n * m 的迷宫模板,它会向四周无限平铺。
模板中:
.是路#是墙S是起点
每次可以上下左右移动一格,不能走到墙上。
问能否从起点走到距离起点无限远的地方。
思路
先看一个小数据暴力:
cpp
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 15;
int n, m;
char mp[MAXN][MAXN];
int sx, sy;
// brute.cpp:小数据暴力解。
// 把原迷宫向四周复制很多份,起点放在中间那份里,
// 如果能走到这个大盒子的边界,就认为可以无限走远。
int dx[4] = {-1, 1, 0, 0};
int dy[4] = {0, 0, -1, 1};
struct Node {
int x, y;
};
int norm_x(int x) {
x %= n;
if (x < 0) x += n;
return x;
}
int norm_y(int y) {
y %= m;
if (y < 0) y += m;
return y;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
while (cin >> n >> m) {
sx = -1;
sy = -1;
for (int i = 0; i < n; i++) {
string s;
cin >> s;
for (int j = 0; j < m; j++) {
mp[i][j] = s[j];
if (mp[i][j] == 'S') {
sx = i;
sy = j;
}
}
}
int tile_radius = n * m + 1;
int H = (tile_radius * 2 + 1) * n;
int W = (tile_radius * 2 + 1) * m;
vector<string> big(H, string(W, '#'));
for (int i = 0; i < H; i++) {
for (int j = 0; j < W; j++) {
char c = mp[i % n][j % m];
if (c == 'S') c = '.';
big[i][j] = c;
}
}
int start_x = tile_radius * n + sx;
int start_y = tile_radius * m + sy;
vector<vector<char> > vis(H, vector<char>(W, 0));
queue<Node> q;
vis[start_x][start_y] = 1;
q.push({start_x, start_y});
bool ok = false;
while (!q.empty()) {
Node cur = q.front();
q.pop();
// 能到达大盒子边界,说明可以走得足够远,
// 对于这个规模的盒子,就等价于能无限走远。
if (cur.x == 0 || cur.x == H - 1 || cur.y == 0 || cur.y == W - 1) {
ok = true;
break;
}
for (int k = 0; k < 4; k++) {
int nx = cur.x + dx[k];
int ny = cur.y + dy[k];
if (nx < 0 || nx >= H || ny < 0 || ny >= W) {
continue;
}
if (vis[nx][ny] || big[nx][ny] == '#') {
continue;
}
vis[nx][ny] = 1;
q.push({nx, ny});
}
}
if (ok) {
cout << "Yes\n";
}
else {
cout << "No\n";
}
}
return 0;
}它把模板向四周复制很多份,在一个足够大的盒子里做 BFS。
这个做法适合帮助理解,也适合对拍,但不能用于正式数据。
本题真正的关键是:
- 迷宫虽然无限大
- 但地形只由
(x mod n, y mod m)决定
也就是说,不同的绝对坐标,可能对应模板中的同一个格子。
于是可以得到一个非常关键的等价条件:
如果同一个模格子,被两个不同的绝对坐标访问到了,那么答案就是 Yes。
原因是这两个位置周围环境完全相同,所以从一个走到另一个的路径可以不断平移复制,进而无限走远。
反过来,如果真的能无限走远,由于模板里只有 n*m 个模格子,根据抽屉原理,也一定会出现这种情况。
所以做法就变成:
- 队列里存绝对坐标
- 访问判重只按模格子判
- 但每个模格子要记录“第一次到达它时的绝对坐标”
- 如果后来同一个模格子又被另一个不同绝对坐标访问到,立刻输出
Yes
如果整次 BFS 结束都没发生这种情况,输出 No。
Python 知识
- Python 的
%对负数仍返回非负余数,next_x % n可直接映射到模板行。 array('i')用 4 字节整数保存最多 225 万个坐标,避免两个 Python 整数列表占用过多内存。- 两个
array加读取指针head组成紧凑队列,避免为每个格子创建坐标元组。 - 网格行保持为
bytes,用 ASCII 码35判断#墙。 /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.md:大网格对象内存与递归限制。
代码
python
import sys
from array import array
UNVISITED = -2_147_483_648
DIRECTIONS = ((-1, 0), (1, 0), (0, -1), (0, 1))
def can_escape(grid, start):
n, m = len(grid), len(grid[0])
total = n * m
first_x = array("i", [UNVISITED]) * total
first_y = array("i", [UNVISITED]) * total
start_x, start_y = start
start_index = start_x * m + start_y
first_x[start_index] = start_x
first_y[start_index] = start_y
queue_x = array("i", [start_x])
queue_y = array("i", [start_y])
head = 0
while head < len(queue_x):
x, y = queue_x[head], queue_y[head]
head += 1
for dx, dy in DIRECTIONS:
next_x, next_y = x + dx, y + dy
row, column = next_x % n, next_y % m
if grid[row][column] == 35:
continue
index = row * m + column
if first_x[index] == UNVISITED:
first_x[index] = next_x
first_y[index] = next_y
queue_x.append(next_x)
queue_y.append(next_y)
elif first_x[index] != next_x or first_y[index] != next_y:
return True
return False
def main():
read = sys.stdin.buffer.readline
answer = []
while True:
header = read().split()
if not header:
break
n, m = map(int, header)
grid = []
start = None
for row in range(n):
line = read().strip()
column = line.find(b"S")
if column != -1:
start = (row, column)
grid.append(line)
answer.append("Yes" if can_escape(grid, start) else "No")
print("\n".join(answer))
if __name__ == "__main__":
main()复杂度
- 时间复杂度:
- 空间复杂度:
总结
这题最核心的转化是:
- 把“无限迷宫能否走出去”
- 变成“同一个模格子是否会对应多个可达绝对坐标”
一旦抓住这个等价关系,整题就只需要在模板大小范围内搜索。