洛谷 - P1363 幻象迷宫
洛谷 - P1363 幻象迷宫
题目链接
实际难度:\(\color{7FF000}{{提高-}}\)
考察知识点
- 搜索 - Flood Fill
思路分析
本题就是在普通的Flood Fill上加了一些特殊性质。
拿到这个题,可能会有的想法:将图中边缘上的点与其对应的点相连(例如:\((1,5)和(n,5)\),\((5,1)和(5,m)\)(当且仅当图中边缘上的点与其对应的点均可到达)。(这也是本人最先想到的思路)
但是这样对吗?显然不对。
对于以下的样例:
5 6
######
##S..#
##.#.#
##...#
######
以上面的方式抽象成图就会得到:
显然应当输出No
,但是依据上面的方法会误判成Yes
。
这是因为:我们只考虑到了环,而没考虑到环的类型(环可以是不跨边界形成的环,也可以是跨边界形成的环)。
所以我们只需维护是否存在一个点,跨过边界能重复到达。
复杂度分析
时间复杂度
\[\begin{aligned}
T(n)&=\underbrace{O(n \times m)}_{寻找起点}+\underbrace{O(n \times m)}_{DFS}\\
&=O(n \times m)
\end{aligned}
\]
空间复杂度
- 主要存储:\(O(n \times m)\)
- 临时变量:\(O(n \times m)\)
- 总空间:\(O(n \times m)\)
C++代码
// Problem: P1363 幻象迷宫
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P1363
// Memory Limit: 125 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include <cstdio> // 用于输入输出操作(如scanf、printf)
#include <cstring> // 用于内存操作(如memset)
using namespace std;
// 方向数组:表示上下左右四个移动方向(左、上、右、下)
// dx[i]和dy[i]分别对应第i个方向的x和y坐标偏移量
const int dx[] = {0, -1, 0, 1}; // x方向偏移(0:左移不影响x,-1:上移x-1,0:右移不影响x,1:下移x+1)
const int dy[] = {-1, 0, 1, 0}; // y方向偏移(-1:左移y-1,0:上移不影响y,1:右移y+1,0:下移不影响y)
const int N = 1510; // 迷宫最大尺寸(题目中迷宫大小不超过1500x1500)
// 全局变量定义
int n, m; // n:迷宫行数,m:迷宫列数
char g[N][N]; // 存储迷宫地图:'.'表示可走,'#'表示障碍物,'S'表示起点
bool flag; // 标记是否找到符合条件的路径(存在幻象环则为true)
int s[N][N][2]; // 记录到达坐标(x,y)时的"循环次数":
// s[x][y][0]为x方向循环次数,s[x][y][1]为y方向循环次数
bool vis[N][N]; // 标记坐标(x,y)是否已被访问过(用于DFS剪枝和环检测)
int sx, sy; // 起点坐标(sx, sy)
/*
* DFS搜索函数:从当前坐标(x,y)出发,探索所有可能路径,检测是否存在幻象环
* x:当前位置的x坐标(迷宫范围内的实际坐标)
* y:当前位置的y坐标(迷宫范围内的实际坐标)
* stepx:x方向的循环次数(每从顶部出界一次+1,从底部出界一次-1)
* stepy:y方向的循环次数(每从右侧出界一次+1,从左侧出界一次-1)
*/
void dfs(int x, int y, int stepx, int stepy) {
// 处理x方向的循环边界:走出迷宫上边界(x<0),从下边界进入
if (x < 0) {
x = n - 1; // 从底部进入(x坐标变为最后一行)
stepx--; // 上移出界,x方向循环次数减1
}
// 处理x方向的循环边界:走出迷宫下边界(x>=n),从上边界进入
if (x >= n) {
x = 0; // 从顶部进入(x坐标变为第一行)
stepx++; // 下移出界,x方向循环次数加1
}
// 处理y方向的循环边界:走出迷宫左边界(y<0),从右边界进入
if (y < 0) {
y = m - 1; // 从右侧进入(y坐标变为最后一列)
stepy--; // 左移出界,y方向循环次数减1
}
// 处理y方向的循环边界:走出迷宫右边界(y>=m),从左边界进入
if (y >= m) {
y = 0; // 从左侧进入(y坐标变为第一列)
stepy++; // 右移出界,y方向循环次数加1
}
// 若当前位置是障碍物('#'),无法继续探索,直接返回
if (g[x][y] == '#') return;
// 若已找到符合条件的路径(flag为true),提前终止搜索
if (flag) return;
// 若当前位置已被访问过(说明形成回路)
if (vis[x][y]) {
// 检查:之前到达该位置时的循环次数(stepx, stepy)与本次是否不同
// 若不同,说明存在"同一位置但循环次数不同"的路径,即存在幻象环
if (s[x][y][0] != stepx || s[x][y][1] != stepy) {
flag = true; // 标记找到有效路径
}
return; // 无论是否符合条件,该路径已探索完毕,返回
}
// 标记当前位置为已访问,并记录到达时的循环次数
vis[x][y] = true;
s[x][y][0] = stepx; // 记录x方向循环次数
s[x][y][1] = stepy; // 记录y方向循环次数
// 向四个方向(左、上、右、下)递归探索
for (int i = 0; i < 4; i++) {
int nx = x + dx[i]; // 新x坐标(未处理边界前)
int ny = y + dy[i]; // 新y坐标(未处理边界前)
// 递归探索下一个位置,循环次数暂不改变(边界处理在递归中完成)
dfs(nx, ny, stepx, stepy);
}
}
int main() {
// 多组测试数据处理(当输入n和m成功时继续循环)
while (scanf("%d%d", &n, &m) != EOF) {
// 初始化访问标记数组(所有位置均未访问)
memset(vis, false, sizeof(vis));
// 初始化循环次数记录数组(默认值为0)
memset(s, 0, sizeof(s));
// 初始化标记:尚未找到符合条件的路径
flag = false;
// 读取迷宫地图(n行,每行m个字符)
for (int i = 0; i < n; i++) {
scanf("%s", g[i]);
}
// 寻找起点'S'的位置,并将其改为'.'(起点视为可走路径的一部分)
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (g[i][j] == 'S') {
sx = i; // 记录起点x坐标
sy = j; // 记录起点y坐标
g[i][j] = '.'; // 起点改为可走路径,避免后续判断干扰
}
}
}
// 从起点开始DFS,初始循环次数设为(1,1)(避免0值干扰,实际只要统一即可)
dfs(sx, sy, 1, 1);
// 根据flag判断是否存在幻象环,输出结果
if (flag) puts("Yes");
else puts("No");
}
return 0;
}