杭电ACM——TempterofTheBone(搜索)

(重新看了一下老久以前的代码,被自己吓了一跳。。。这,字也太多了吧。。。所以如果觉得太繁琐了了不想看,最后面还有一个代码,看着可能会比较舒服)

这是一道有趣的迷宫题,也是一道搜索的题目,有点类似于深度搜索,可以通过递归来实现。由于递归很容易超时,所以务必进行“剪枝”!!!
一、当迷宫中可走的方块
二、“奇偶性剪枝”(比较常用,要记一下):如果“S”的坐标(xs,ys),“D”的坐标(xd,yd)与T满足|xs-xd|+|ys-yd|,T两者的奇偶性不相同,则一定不可能满足要求,直接输出“NO\n”,可以自己画一个有0,1构成的矩阵思考一下,所以当遇到从 0 走向 0 ,1走向1,但是要求时间是奇数的,或者, 从 1 走向 0 ,0走向1,但是要求时间是偶数的,可以直接判断不可达!
三、其它“剪枝”将于代码中解释。

思路:构造一个函数,模拟从四个方向,在迷宫中行走,因此我们需要事先写好一个数组dir[4][2],来模拟上下左右四个方向的行走。当然,必须注意到有些时候是走不了的,比如,碰到“墙”了,“越界”了,以及返回走是不行的。
代码见下:

#include<stdio.h>
#include<string.h>
#include
char s[10][10];      //用以记录迷宫
int dir[4][2]={{1,0},{-1,0},{0,1},{0,-1}};//模拟上下左右移动 
int flag,flag_move,move;  //flag表示能否到达“D”,flag_move表示能否到达“D”的同时,使时间恰好为T,move表示走的步数
int N,M,T;
int xs,ys,xd,yd;  //前两个表示“S”的坐标,后两个表示“D”的坐标
void mov(int sx,int sy) //模拟迷宫中行走的函数
{
int i,j;
if(s[sx][sy]=='X') return;    //遇到墙不能走,结束此次调用
if(s[sx][sy]=='0') return;   //越界了也不可以,结束此次调用
if(s[sx][sy]=='D') //找到出口“D”了
{
if(move==T) flag_move=1;  如果此时走的步数为T,flag_move=1
flag=1;return;
} // printf("%d %d __  ",sx,sy);    这是在dig bug
if(move==T)return;     //达到规定的步数了,结束此次调用——这也是“剪枝”内容之一
for(i=0;i<=3;i++) //从当前位置,四个方向都走一遍
{ move++;      //走一次,move+1
s[sx][sy]='X';   //走过了,就不能返回,那就把它设为“墙”
mov(sx+dir[i][0],sy+dir[i][1]);    //试试向一个方向走一下
s[sx][sy]='.';  //当这个方向行不通时,或者遇到了move=T,或者已经走到了出口“D”,才会执行下面 操作,这个要理解清楚
move--;    //当行不通时,move-1,因为那一步不算有效步数
if(flag&&flag_move) return;  //找到了就不用再继续查找了!!!!否则会超时!!! 这是很重要的“剪枝”之一!(一次因为少了这一句,就超时了)
}
}
int main()   //main函数控制输入和输出
{
int i,j,cnt; //cnt记录“X”的个数
int x,y;
while(scanf("%d %d %d",&N,&M,&T)&&N&&M&&T)
{
getchar(); //当题中输入有字符串时,要小心,这里的getchar()是用来吃掉输入N,M,T后的过行符
cnt=0;  //以下几行都是初始化数据,都要在循环里面进行
xs=0;ys=0;xd=0;yd=0;move=0;flag=0;flag_move=0;
x=0;y=0;
memset(s,'0',sizeof(s));// printf("%c\n",s[2][0]);
for(i=1;i<=N;i++)
{
   for(j=1;j<=M;j++)
   {
    scanf("%c",&s[i][j]);  //如果没有上面的getchar(),这个过行符就会被当成字符串s的元素保存
    if(s[i][j]=='X') cnt++;
    else if(s[i][j]=='S')
    {
    xs=i;ys=j; //printf("%d %d S\n",xs,ys);
}
else if(s[i][j]=='D')
{
xd=i;yd=j; //printf("%d %d D\n",xd,yd);
}
// printf("%d j\n",j);
}
getchar();   //同理,吃掉输入的过行符
}
// printf("%d\n",cnt);        dig bug
x=fabs(xs-xd);y=fabs(ys-yd);
if(N*M-cnt
else if((x+y)%2!=T%2) printf("NO\n");   //看“剪枝”二
else
{
mov(xs,ys);//printf("%d move %d flag\n",move,flag);   dig bug
if(flag&&flag_move) printf("YES\n"); 
else printf("NO\n");
}
}
return 0;
 } 

小结:其实本题数据还是很小的,所以才可以用这个算法,否则将会超时,但没有“剪枝”的话,也会超时,所以“剪枝”真的灰常重要!
dig bug也很重要。
复杂的问题,可以考虑用自上而下的方法来编代码,用函数实现模块化的解决问题,main函数控制输入输出,其它函数表示相应功能。
/

//第三次修改。感觉以前写的代码挺一般的,现在没怎么用C/C++了都比以前写得好
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
char maze[10][10];
int dir[4][2] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
int sx, sy, dx, dy;

bool dfs(int posx, int posy, int n, int m, int left_moves)
{
	if(!left_moves && posx == dx && posy == dy)
		return true;
		
	for(int i = 0; i < 4; ++i)
	{
		int tmpx = posx + dir[i][0], tmpy = posy + dir[i][1];
		if(tmpx >= 0 && tmpx < n && tmpy >= 0 && tmpy < m)	// 没有越界
		{
			if(maze[tmpx][tmpy] != 'X')
			{
				maze[posx][posy] = 'X';
				if(dfs(tmpx, tmpy, n, m, left_moves - 1))
					return true;
				maze[posx][posy] = '.';
			}
		} 
	}
	return false;
}

int main()
{
	int n, m, t;
	while(cin>>n>>m>>t)
	{
		if(!n && !m && !t)
			break;
		
		int walls_number = 0; 
		for(int i = 0; i < n; ++i)
		{
			cin>>maze[i];
			for(int j = 0; j < m; ++j)
				if(maze[i][j] == 'S')
				{
					sx = i;
					sy = j;
				}
				else if(maze[i][j] == 'D')
				{
					dx = i;
					dy = j;
				}
				else if(maze[i][j] == 'X')
					walls_number++;
		}
		
		int shortest_distance = int(abs(dx + dy - sx - sy));
		if(shortest_distance > t)	// 剪枝1 
			cout<<"NO"<<endl;
		else if(shortest_distance % 2 != t % 2)	// 剪枝2,奇偶性剪枝 
			cout<<"NO"<<endl;
		else if(shortest_distance > n * m - walls_number)	// 剪枝3,墙太多 
			cout<<"NO"<<endl;
		else if(dfs(sx, sy, n, m, t))
			cout<<"YES"<<endl;
		else
			cout<<"NO"<<endl;
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值