HDU1728 逃离迷宫 DFS

逃离迷宫
Time Limit:1000MS     Memory Limit:32768KB     64bit IO Format:%I64d & %I64u
Submit  Status

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, y 2对应行。 
 

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()剪枝中为什么相等的情况不能剪掉(先看代码去):

如图:HDU1728 逃离迷宫 DFS_第1张图片

很明显,如果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",....),不然不行。



你可能感兴趣的:(搜索)