逃离迷宫
Time Limit:1000MS Memory Limit:32768KB 64bit IO Format:%I64d & %I64u
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, x 1, y 1, x 2, y 2 (1 ≤ k ≤ 10, 1 ≤ x 1, x 2 ≤ n, 1 ≤ y 1,
y 2 ≤ m),其中k表示gloria最多能转的弯数,(x 1, y 1), (x 2, y 2)表示两个位置,其中x 1,x 2对应列,y 1, 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
这个题卡在不知道怎么判是否转弯,不过看了大神的解题就懂了,程序里的(dir!=-1 && i!=dir)就是用来判断是否转弯了。
本题我是用的dfs,找到终点时并不晕就返回真,程序并没有遍历整个图。并且在走重时会根据在重点的转弯值turn判断以选优,
这在程序中已实现,并作为剪枝,注意本题剪枝很重要,不然会超时的!!
下面来解释一下bfs()中if()剪枝中为什么相等的情况不能剪掉(先看代码去):
如图:
很明显,如果k=1的话,那么途中红线是不符合题意的,我们来谈论最中心的那个点(下面我们叫它中心点):
走红线时,在中心点的turn值是1,当然要去终点的到还需要再转一次,就不符合题意,一次放弃这条路线,但是中心点的turn就此存在了,并且是1.
当再走蓝线时,走到该点时turn值也是1,此时人没晕,一直走是可以走到终点的,所以这种情况是应该输出“yes”的。
但如果你把相等的情况剪掉了,那么就是相当于把蓝线这种可能给否定了,那自然就错了。
造成这种情况的原因其实很简单,因为不同路线的到达相同的点时,因为来的时候方向不同,要是再朝相同的方向(即终点的方向)走时,有的需要转一次;有的则不需要,接着原来的方向走就行。
代码如下:
#include <stdio.h> #include <string.h> #include <math.h> #define Max 999999 #include <algorithm> using namespace std; struct point { int x,y; }s[20000]; char map[110][110]; int turn[110][110]; int n,m,x2,y2,ok,k; int dx[4]={1,-1,0,0}; int dy[4]={0,0,1,-1}; void dfs(int x,int y,int dir) { int i,xx,yy; if(x==x2 && y==y2 && turn[x][y]<=k) //成功的时候返回 { ok=1; return ; } if(turn[x][y]>k) //大于k时已晕,不行 return ; if(x!=x2 && y!=y2 && turn[x][y]==k) //如果(x,y)和终点(x2,y2),既不在同一行也不在同一列,那么要想到终点至少需要转一次,但现在已经转够k次了,故不行 return ; for(i=0;i<4;i++) { xx=x+dx[i]; yy=y+dy[i]; if(xx<=0 || yy<=0 || xx>m || yy>n || map[xx][yy]=='*') continue; //注意!!!下面这行中的turn[xx][yy]是表示(xx,yy)已经由别的路线走过了,并记录了turn[xx][yy],现在需要比较这次走到(xx,yy)和由别的路线走到 //(xx,yy)时,两个的turn值,如果上次的比这次的小,说明这次不行,故要continue; if(turn[xx][yy]<turn[x][y]) //这里相等的情况不能剪掉,原因开头已解释 continue; //下面这条if和上面的差不多,目的是:如果从(x,y)走一步到(xx,yy)需要转一次话,并且转过之后turn[x][y]+1依然比turn[xx][yy]大的话,也不符合 if(dir!=-1 && i!=dir && turn[xx][yy]<turn[x][y]+1) continue; //这两个if语句剪枝很重要,没有的话就超时 if(dir!=-1 && i!=dir) turn[xx][yy]=turn[x][y]+1; else turn[xx][yy]=turn[x][y]; map[xx][yy]='*'; //如果这里能走,就把这里变成不能走,然后再从这里开始递归,其实就是起到vis[][] 的作用,会用vis的话就不用追究了 dfs(xx,yy,i); map[xx][yy]='.'; //这里再变成'.',是为了不影响其他的递归过程,因为其他路线可能还要从这里过 if(ok) return ; } } int main() { int i,j,t,x1,y1; scanf("%d",&t); while(t--) { scanf("%d%d",&m,&n); for(i=1;i<=m;i++) for(j=1;j<=n;j++) scanf(" %c",&map[i][j]); scanf("%d%d%d%d%d",&k,&y1,&x1,&y2,&x2); //注意,这里是先接y,再接x,我被坑了好长时间 memset(turn,Max,sizeof(turn)); //因为在dfs()中剪枝要去最小的转弯次数,所以turn要初始化成最大 ok=0; turn[x1][y1]=0; dfs(x1,y1,-1); if(ok) printf("yes\n"); else printf("no\n"); } return 0; }
另外需要注意的就是,scanf时,%c前面加个空格,即scanf(" %c",....),不然不行。