读者可先学习深搜解决全排列问题
bfs和dfs的区别
- bfs更适合解决带有“最少”,“最短”字样的题
- dfs更适合解决带有“刚刚”,“刚好”字样的题
在练习迷宫问题之前,先介绍一下奇偶性剪枝:
- 若地图上某点的行号加列号为奇数,则将其定义为奇点;
- 若地图上某点的行号加列号为偶数,则将其定义为偶点;
- 行与列从(0,0)开始
- 奇点用1表示
- 偶点用0表示
通过以上定义可以把一个地图表示为下图(01相间):
不难发现:
在每一步消耗一个单位时间的情况下:
- 0的下一步只能走向1
- 1的下一步只能走向0
- 从0走向1需要的步数(时间)一定是奇数
- 从1走向0需要的步数(时间)一定是奇数
- 从0走向0需要的步数(时间)一定是偶数
- 从1走向1需要的步数(时间)一定是偶数
通过这个性质可以对一些问题进行剪枝,称奇偶性剪枝。
迷宫问题原题链接
题目大意
小狗在一个古老的迷宫中发现了一根骨头,这使他非常着迷。 但是,当他捡起它时,迷宫开始摇晃,小狗可以感觉到地面下沉。 他意识到骨头是一个陷阱,他拼命试图摆脱这个迷宫。
迷宫是一个矩形,大小为N×M。迷宫中有一扇门。 刚开始时,门是关闭的,它将在第T秒打开一小段时间(少于1秒)。 因此,小狗必须在第T秒精确到达门。 每秒钟,他可以从一个块移动到上,下,左和右相邻的块之一。 一旦他进入一个块,该块的地面将开始下沉并在下一秒消失。 他不能在一个块停留超过一秒钟,也不能移动到一个已经去过的块。 可怜的小狗可以生存吗? 请帮助他。
输入
输入包含多个测试用例。 每个测试用例的第一行包含三个整数N,M和T(1 <N,M <7; 0 <T <50),分别表示迷宫的大小和门打开的时间。 。 接下来的N行给出迷宫的布局,每行包含M个字符。 字符是以下之一:
“ X”:狗无法进入的一堵墙
‘S’:小狗的起点
‘D’:门
‘.’:一个空块
输入以三个0终止。 该测试用例将不被处理。
输出
对于每个测试用例,如果小狗可以存活,则在一行中打印“是”,否则打印为“否”。
样例输入
4 4 5
S.X.
…X.
…XD
…
3 4 5
S.X.
…X.
…D
0 0 0
样例输出
NO
YES
本题中遇到的剪枝
- 如果所有的迷宫块 - 障碍物的数量 < 时间,说明小狗会提前到达门前,狗必死无疑。
- 两点间的最短距离为abs(x1 - x2) + abs(y1 - y2)
- temp % 2为奇偶性剪枝,以下图为例。 若此时小狗走到了黄色的点上,并且此时剩余6个单位时间逃生的门才会打开。而终点与目前所在点的距离只有1个单位,由si到达di也只需要一个单位时间。因此,temp = 6 - 1 = 5。此时到达了door还剩余5个时间单位。又因为失败的有两个点 1. 是剩余时间不够 2. 是到达了door还有剩余时间。假设到了点door,还剩下3个时间,则剩下三个时间没法从Door,到其他点,再到达door,只有偶数时间可以。
AC代码及详细注释
#include <cstdio>
#include <string>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
int n,m,t;//n,m分别表示矩阵的行和列,t表示开门的时间
int wall;//障碍物的数量(有利于剪枝)
int si,sj;//小狗的开始位置(start)
int di,dj;//门/终点的位置(door)
int escape;//表示小狗是否可以逃脱迷宫
char map[10][10];//字符数组,用来存储迷宫
int direct[4][2] = {{-1,0},{1,0},{0,-1},{0,1}};//方向数组,用来实现移动方向,分别表示上下左右
void dfs(int si,int sj,int time)
{
if (si > n || sj > m || si <= 0 || sj <= 0) return;//行不大于n,列不大于m,行和列不小于等于0
if (si == di && sj == dj && time == t) escape = 1;//到达门前且时间刚好,门刚好打开,此时小狗可以逃出
if (escape) return;//逃出迷宫
int temp;
temp = (t - time) - abs(si - di) - abs(sj - dj);//t - time表示当前剩余时间,每走一步表示一个单位时间
if (temp < 0 || temp % 2) return;//temp < 0表示当前的剩余时间结束前不能走到门口
for (int i = 0;i < 4;i++)//4次循环表示四个方向
{
if (map[si + direct[i][0]][sj + direct[i][1]] != 'X' )//如果某个方向不是障碍物
{
map[si + direct[i][0]][sj + direct[i][1]] = 'X';//当前位置开始下陷
dfs(si + direct[i][0],sj + direct[i][1],time + 1);//向这个方向尝试
map[si + direct[i][0]][sj + direct[i][1]] = '.';//回溯
}
}
return;
}
int main()
{
while (cin >> n >> m >> t && (n + m + t) )
{
wall = 0;
for (int i = 1;i <= n;i++)
{
for (int j = 1;j <= m;j++)
{
cin >> map[i][j];//读入地图
if (map[i][j] == 'S')//找到小狗的开始位置
{
si = i;
sj = j;
}else if (map[i][j] == 'D')
{
di = i;//找到门的位置
dj = j;
}else if (map[i][j] == 'X')
{
wall += 1;//墙/障碍的数量
}
}
}
if (n * m - wall < t) //剪枝
{
cout << "NO" << endl;
continue;
}
escape = 0;//escape必须初始化
map[si][sj] = 'X';//当前位置开始下陷
dfs(si,sj,0);//初始位置si,sj和初始时间0
if (escape) cout << "YES" << endl;//可以逃脱输出YES否则输出NO
else cout << "NO" << endl;
}
return 0;
}