题目链接:
Problem Description
给定一个m × n (m行, n列)的迷宫,迷宫中有两个位置,gloria想从迷宫的一个位置走到另外一个位置,当然迷宫中有些地方是空地,gloria可以穿越,有些地方是障碍,她必须绕行,从迷宫的一个位置,只能走到与它相邻的4个位置中,当然在行走过程中,gloria不能走到迷宫外面去。令人头痛的是,gloria是个没什么方向感的人,因此,她在行走过程中,不能转太多弯了,否则她会晕倒的。我们假定给定的两个位置都是空地,初始时,gloria所面向的方向未定,她可以选择4个方向的任何一个出发,而不算成一次转弯。gloria能从一个位置走到另外一个位置吗?
Input
第1行为一个整数t (1 ≤ t ≤ 100),表示测试数据的个数,接下来为t组测试数据,每组测试数据中,
第1行为两个整数m, n (1 ≤ m, n ≤ 100),分别表示迷宫的行数和列数,接下来m行,每行包括n个字符,其中字符'.'表示该位置为空地,字符'*'表示该位置为障碍,输入数据中只有这两种字符,每组测试数据的最后一行为5个整数k, x1, y1, x2, y2 (1 ≤ k ≤ 10, 1 ≤ x1, x2 ≤ n, 1 ≤ y1, y2 ≤ m),其中k表示gloria最多能转的弯数,(x1, y1), (x2, y2)表示两个位置,其中x1,x2对应列,y1, y2对应行。
Output
每组测试数据对应为一行,若gloria能从一个位置走到另外一个位置,输出“yes”,否则输出“no”。
Sample Input
2 5 5 ...** *.**. ..... ..... *.... 1 1 1 1 3 5 5 ...** *.**. ..... ..... *.... 2 1 1 1 3
Sample Output
no
yes
题目分析:
一、这个题目比较坑的地方就是输入,是先输入的最大转弯次数,然后在输入列和行(和正常的使用是反着的)。这个是杭电里面的题,输入坐标时我用了y1这个变量,提交竟然显示y1重复定义,好像有函数库y1这个函数,不能再用这个变量了,真是长见识了。。。。
二、我一开始的思路就是用bfs+三维数组标记,第三维用来存储每个点的转弯次数,一想好像也有没啥不对就开始写了,写着写着发现不对了。假设三维数组标记某个点的转弯次为3(按着路线搜索过来的走到终点还要拐一次弯),那下面在搜索到这个地方刚好转弯次数也为3了,而且终点就在眼前,不拐弯就都能到终点,那这样不是完蛋了吗,没法用最少的拐弯次数到达终点了(虽然这题不是求最小转弯次数的,但在一定的转弯次数下,可能无法到达终点了)。这个问题就出现在某个点的转弯次数涉及他前一步从哪走过来的,而且这点的方向还影响下个点的转弯次数,想了好久也没想到解决办法,,,所以这时候这时候就认为bfs+三维数组的方法就行不通了。
三、在网上见到了其中一个大佬的思路,用bfs+二维数组标记写的,这里面二维数组的不是标记某个点走过还是没走过,而且用二位数组的的(x,y)代表某个点的坐标,二位数组的值book[ x ] [ y ](我习惯用book数组当作标记数组)来记录某个点的转弯次数,那这样以来就解决那个问题了。假设搜索到某个点,这时候有两种情况,1.没搜索过,这时候肯定可以搜;2.走过,但是这次搜索到这个点用的转弯次数小于等于上次走到这的转弯次数(这个等于很关键,解决上面说的问题)。而且按的方法这样搜到终点时,所走的步数还是最少的(特别关键,这个bfs求走迷宫求最小步数最关键的地方);如果想求最小转弯次数,那队列变成关于转弯次数的优先队列,代码上稍微有点改动就可以了(这时候走到终点时,步数就不能保证数最小的了)
这题的AC代码:
#include<iostream> #include<queue> #include<string.h> using namespace std; char a[105][105]; int book[105][105]; int nex[4][2]={0,1,1,0,0,-1,-1,0};//0右1下2左3上 int n,m;//行,列 int x1,yy1,x2,y2,k; struct dd { int x; int y; int s;//转弯的次数 int turn;//从哪个方位走过来的 dd (int xx,int yy,int ss,int tt) { x=xx; y=yy; s=ss; turn=tt; } }; void bfs(int x,int y) { queue<dd> q; book[x][y]=0; dd temp(x,y,0,-1); q.push(temp); while(q.empty()==0) { temp=q.front(); q.pop(); for(int i=0;i<4;i++) { int tx=temp.x+nex[i][0]; int ty=temp.y+nex[i][1]; int ts;//当前转弯次数 if(temp.turn==i||temp.turn==-1) ts=temp.s; else ts=temp.s+1; int tt=i;//当前方向 if(tx<=0||tx>n||ty<=0||ty>m) continue; if(ts>k) continue; if(a[tx][ty]=='.') { if(book[tx][ty]==-1||ts<=book[tx][ty])//没走过或者这次走到这的转弯次数小于等于以前走过的转弯次数 { book[tx][ty]=ts; q.push(dd(tx,ty,ts,tt)); } } if(tx==x2&&ty==y2) { cout<<"yes"<<endl; return ; } } } cout<<"no"<<endl; return ; } int main() { int t; cin>>t; while(t--) { memset(book,-1,sizeof(book));//多组的,记得重置零 cin>>n>>m; for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) cin>>a[i][j]; cin>>k>>yy1>>x1>>y2>>x2; bfs(x1,yy1); } return 0; }
心得:
一、本来一直以为三维数组标记比二位数组标记使用范围广呢,这就没啪啪打脸了,当然通过这题也不是说二维数组标记的方法比三维的优越性更好,做完题也想了好多,最后没想出来那种方法更好一些,
下面放一个bfs+三维数组标记的链接: bfs+三维数组标记
在这个题里面就要用三维数组标记,用二维数组标记+优先队列也不行。。。
二、bfs的题做多了,有个地方千万不要弄混:就像最简单的题,走迷宫求最短路径,这时候第一想法就是bfs,直接就能求出来最短路径了,但你要想想为啥bfs能求出来最短路径,而用简单的dfs求最短路径容易超时呢,下面说一下为啥bfs一遍就能求出来最短路径了呢。
1.bfs简单来说就是1-——>4——>16这样变得,一个可以搜索到周围的四个,四个可以搜索到周围的16个,这就所谓的层层搜索,在搜到第二层的4个时,这4个步数都是1,在搜索第三层的16个时,步数都是2,后面的一次递推,每次都是把当前层的坐标搜索完,再去搜索下一层。每一层的步数都是相同的,这样才保证了搜到终点时,步数肯定是最少的,而且按最简单的情况来说,一个点一般只搜索一次就可以了,而dfs会搜索多次。
2.当然有简单情况,也有复杂一点的情况,上面的简单的情况就是每走一步花的时间是一样的(最短路径和最短时间类似的,这里我就不做区分了,理解就行),
那如果每走一步花的时间不一样该怎么办的,就像这个例子里 bfs+优先队列 ,走一步花的时间可能是1或者2,那第一层,第二层,第三层这种的,走到每一层的时间就不一样了,那走到终点就不能保证是最短时间了,这时候就需要优先队列出场了,把时间短的优先走,这时候就可以保证走到终点的时间最短了。
最基本的走迷宫求最短路径就,没有所谓的优先队列,就是用的最基本的队列,按照先进先出的方式一层一层搜索的,只不过在搜索过程中队列前面的路径肯定不会比队列后面的路径长(会出现相等的情况)。这样可以默认为关于路径的优先队列(并不是优先队列),这样下次求最短路径就可以有意识地用bfs了,而且还知道要着重考虑队列优先级的问题。
用bfs时一定要考虑的两个问题
1.队列优先级的问题,这个涉及搜到答案时是否是最短路径(队列的优先级是啥,搜到答案是啥是最优的)。
2.数组标记问题,动手写代码之前一定要考虑怎样数组标记,假如用某种数组标记会不会出问题。