【hdoj_1010】Tempter of the Bone(迷宫+剪枝)

题目:http://acm.hdu.edu.cn/showproblem.php?pid=1010

题目大意:给出一个迷宫(含起点和终点),要求找出一条路径,这条路径的长度必须为某个规定的长度.


在做本题之前,先学习了一下迷宫问题:http://blog.csdn.net/ten_sory/article/details/66975811


在理解迷宫问题的基础上,再做本题.本题的难点就是剪枝的问题.如果只用一般的DFS+回溯的方法求解这个问题,一定会超时,所以需要一些剪枝技巧.


1.奇偶剪枝:如果当前位置为(x,y),终点为(dx,dy),要求你在T步内从(x,y)走到(dx,dy).剪枝就是确定是否存在在T步内完成(x,y)到(dx,dy)的可能性.(x,y)——>(dx,dy)的最少步骤为abs(dx-x)+abs(dy-y),如下图,就是两点之间的曼哈顿距离.



当然,这个最短距离(设为path1)未必可以走得通,所以存在其他的路径.可以肯定的是,其他任意一条路径(设为path2)的长度一定和path1的长度的奇偶性是相同的.因为,path1和path2的起点和终点在纵轴一定相等,如果path2在纵轴上多走了一步,一定要在纵轴上往回走一步,只有这样,才能到达终点.同理,横轴也是一样.

根据这个结论,可以得出,(x,y)到(dx,dy)的最短路径的长度(步数),一定和所规定的步数的奇偶性相同.如果奇偶性不同,终止这条路径的探测.


2.小小剪枝:如果规定时间为T,而障碍物个数为wall个,则如果n*m-wall=可走的点的个数<=T,那么一定不存在某条可行路径的长度为T,终止即可.


代码如下:

#include<iostream>
using namespace std;

int flag;
char maze[10][10];
int vis[10][10];
int n,m,T,t;
int sx,sy,dx,dy;

int dir[4][2] = {{1,0},{0,1},{-1,0},{0,-1}};//4个方向

int abs(int x){return x>0?x:(-x);}

void dfs(int x,int y,int t)
{
	if(x==dx && y==dy && t==T)//到达终点
	{
		flag = 1;
		return;
	}
	if(x<0 || x>=n || y<0 || y>=m)//出界
		return;

	/*奇偶剪枝*/
	int temp1 = abs(dx-x) + abs(dy-y);
	int temp2 = abs(T-t);//剩余时间
	int temp = abs(temp1-temp2);
	if(temp%2!=0)
		return;

	//以当前位置(x,y)位置为起点,从上,下,左,右4个方向探测
	for(int i=0;i<4;i++)
	{
		int nx = x + dir[i][0];
		int ny = y + dir[i][1];//获取新坐标(nx,ny)

		if(0<=nx && nx<n && 0<=ny && ny<m)//(nx,ny)没有出界
		{
			if(maze[nx][ny]!='X' && vis[nx][ny]==0)//位置(nx,ny)不是障碍,而且没有被访问过
			{	
				vis[nx][ny] = 1;//设置(nx,ny)为[访问过]状态
				dfs(nx,ny,t+1);//以(nx,ny)为起点,进行下一步探测,步数+1
				
				if(flag)	return;//*****这里下面重点说明
				
				vis[nx][ny] = 0;//回溯
			}
		}
	}
}

int main()
{
	//freopen("in.txt","r",stdin);
	while(1)
	{
		scanf("%d%d%d",&n,&m,&T);
		if(!n && !m && !T)
			break;

		int wall=0;

		for(int i=0;i<n;i++)
		{
			cin >> maze[i];
			for(int j=0;j<m;j++)
			{
				vis[i][j] = 0;
				if(maze[i][j]=='X')    wall ++;
				else if(maze[i][j]=='S')	sx=i,sy=j,vis[i][j]=1;
				else if(maze[i][j]=='D')	dx=i,dy=j;
			}
		}

		if(n*m-wall<=T)//剪枝,可以节省一点时间
		{
			printf("NO\n");
			continue;
		}

		t = 0;
		flag = 0;
		dfs(sx,sy,t);
		if(flag)
			printf("YES\n");
		else
			printf("NO\n");
	}

	return 0;
}

一个关于递归和回溯法的说明:

递归+回溯法中,如果一个探测return了,只是本次递归结束了,之后回溯,进行下一次递归.所以某次递归的return不代表整个递归函数的return.

本题中,只要某次递归找到一个符合要求的路径,就结束整个函数,而不仅仅是结束此次递归.

所以,本题在回溯之前,需要判断一下是否已经找到了符合要求的路径,即:在dfs(...)之后,立即判断if(flag)  return;


本文剪枝策略参考:http://www.cnblogs.com/grubbyskyer/p/3855533.html

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值