2017网易游戏雷火盘古实习生招聘笔试真题 推箱子

2018.3.19 

此题我用了广度优先搜索(BFS)的方法.广度优先搜索不仅仅可以使用在图的遍历中,而且可以用于求解复杂问题的最优解。

  • 引子
gnatius被魔王抓走了,有一天魔王出差去了,这可是Ignatius逃亡的好机会.魔王住在一个城堡里,城堡是一个A*B*C的立方体,可以被表示成A个B*C的矩阵,刚开始Ignatius被关在(0,0,0)的位置,离开城堡的门在(A-1,B-1,C-1)的位置,现在知道魔王将在T分钟后回到城堡,Ignatius每分钟能从一个坐标走到相邻的六个坐标中的其中一个.现在给你城堡的地图,请你计算出Ignatius能否在魔王回来前离开城堡(只要走到出口就算离开城堡,如果走到出口的时候魔王刚好回来也算逃亡成功),如果可以请输出需要多少分钟才能离开,如果不能则输出-1. 
输入: 
输入数据的第一行是一个正整数K,表明测试数据的数量.每组测试数据的第一行是四个正整数A,B,C和T(1<=A,B,C<=50,1<=T<=1000),它们分别代表城堡的大小和魔王回来的时间.然后是A块输入数据(先是第0块,然后是第1块,第2块……),每块输入数据有B行,每行有C个正整数,代表迷宫的布局,其中0代表路,1代表墙。 
输出: 
对于每组测试数据,如果Ignatius能够在魔王回来前离开城堡,那么请输出他最少需要多少分钟,否则输出-1. 
样例输入: 

3 3 4 20 
0 1 1 1 
0 0 1 1 
0 1 1 1 
1 1 1 1 
1 0 0 1 
0 1 1 1 
0 0 0 0 
0 1 1 0 
0 1 1 0 
样例输出: 

11

本题就是利用BFS的经典题目,首先来分析这个问题

查找空间:所有(0,0,0)到点(A-1,B-1,C-1)合法的行走路径

查找目标:找到通往出口的最短路径

查找方法:将人物的三维位置坐标以及所花费的时间作为一个四元组(x,y,z,t),这就把查找所有路径转化成了对状态的搜索,即查找(A-1,B-1,C-1,t),对于具体的某一状态,可以通过扩展状态来遍历查找所有之后的状态,比如(x,y,z,t)在没有障碍物的情况下可以扩展为(x+1,y,z,t+1)(x-1,y,z,t+1)(x,y+1,z,t+1)(x,y-1,z,t+1)(x,y,z+1,t+1)(x,y,z-1,t+1)这六个状态,所以我们可以想象从(0,0,0,0)扩展到末状态的情形。

这种扩展的形式抽象成数据结构就是树,由(0,0,0,0) 为根节点,在理想状态下可以生出6个孩子,分别为(x+1,y,z,t+1)(x-1,y,z,t+1)(x,y+1,z,t+1)(x,y-1,z,t+1)(x,y,z+1,t+1)(x,y,z-1,t+1),而这6个孩子又有机会扩展生出6*6个,如此延续,而我们的任务就是按层次遍历这棵树,这样就可以尽可能早的找到目标状态。

对于扩展,我们需要运用一些技巧来剪支,比如遇到障碍的支路需要删除,之前访问的过的位置在后来访问时必然不可能是最短路径的组成部分,这也可删除,这可以用一个bool类型的数组来记录其是否已经进行过

#include 
#include 
using namespace std;

bool mark[50][50][50] ;
int maze[50][50][50] ;
struct Pos
{
	int x,y,z ;
	int t ;
};
queue Q ;

int go[][3] =
{
     1 , 0 , 0 ,
   -1 , 0 , 0 ,
    0 , 1 , 0 ,
    0 ,-1 , 0 , 
    0 , 0 , 1 ,
    0 , 0 ,-1 
};

int BFS(int a , int b ,int c)
{
	while(Q.empty() == false)
	{
		Pos now = Q.front();
		Q.pop();
		for(int i = 0 ; i < 6 ; i++)
		{
			int nx = now.x + go[i][0];
			int ny = now.y + go[i][1];
			int nz = now.z + go[i][2];
			//如果跑到范围外面去了
			if(nx < 0 || ny < 0 || nz < 0 || nx >= a || ny >= b || nz >= z) continue ;
			//如果有障碍物
			if(maze[nx][ny][nz] == 1) continue ;
			//如果之前已经来过这个点了
			if(mark[nx][ny][nz] == true) continue ;

			Pos temp ;
			temp.x = nx ;
			temp.y = ny ;
			temp.z = nz ;
			temp.t = now.t + 1 ;
			Q.push(temp);
			mark[nx][ny][nz] = true ;
			if(nx == a-1 && ny == b-1 && nz == c-1)
				return temp.t ;
		}
	}
	return -1 ;
}
int main()
{
	int T ;
	scanf("%d",&T);
	while(T--)
	{
		int a,b,c,t ;
		scanf("%d%d%d%d",&a,&b,&c,&t);
		for(int i = 0 ; i < a ; i++)
		{
			for(int j = 0 ; j < b ; j++)
			{
				for(int k = 0 ; k < c ; k++ )
				{
					scanf("%d",&maze[i][j][k]);
					mark[i][j][k] = false ;
				}
			}
		}
		//由于Q开始不一定为空,所以要置空
		while(Q.empty() == false) Q.pop();
		mark[0][0][0] = true ;
		Pos start ;
		start.x = start.y = start.z =start.t = 0 ;
		Q.push(start);
		int ans = BFS(a,b,c);
		if(ans <= t) printf("%d\n",ans);
		else printf("-1\n");
	}
	return 0 ;
}

  • 推箱子
大家一定玩过“推箱子”这个经典的游戏。具体规则就是在一个N*M的地图上,有1个玩家、1个箱子、1个目的地以及若干障碍,其余是空地。玩家可以往上下左右4个方向移动,但是不能移动出地图或者移动到障碍里去。如果往这个方向移动推到了箱子,箱子也会按这个方向移动一格,当然,箱子也不能被推出地图或推到障碍里。当箱子被推到目的地以后,游戏目标达成。现在告诉你游戏开始是初始的地图布局,请你求出玩家最少需要移动多少步才能够将游戏目标达成。

那么对于推箱子,思考方式也类似,只不过有一点需要注意,由于推箱子里有两个重要的元素,人和箱子,所以要mark人和箱子的坐标,mark[x1][y1][x2][y2],一开始只mark[x1][y1]了人的坐标信息这样会导致错误。

#include 
#include 
using namespace std ;
 
bool mark[8][8][8][8] ;
char game[8][8];
 
int go[][2] =
{
     1 , 0 ,
    -1,  0 ,
     0 , 1 ,
     0 ,-1
};
 
struct Pos
{
    int x1 , y1 ,x2 ,y2 ;
    int t ;
};
 
queue Q ;
 
int BFS(int N , int M)
{
    while(Q.empty() == false)
    {
        Pos now = Q.front();
        Q.pop();
        for(int i = 0 ; i < 4 ; i++)
        {
            int nx = now.x1 + go[i][0] ;
            int ny = now.y1 + go[i][1] ;
            if(nx < 0 || nx >= N || ny < 0 || ny >= M ) continue ;
            if(game[nx][ny] == '#' ) continue ;
            //到这里说明人可以走到这个点,下面就判断人是否和箱子有重合
            if( nx == now.x2 && ny == now.y2)
            {
                //推算箱子的坐标
                int nxx = nx + go[i][0] ;
                int nyy = ny + go[i][1] ;
                //如果1 越界 2 障碍 3 已访问
                if( nxx < 0 || nxx >= N || nyy < 0 || nyy >= M) continue ;
                if( game[nxx][nyy] == '#' ) continue ;
                if( mark[nx][ny][nxx][nyy] == true ) continue ;
                //否则
                if( game[nxx][nyy] == '@') return now.t + 1 ;
                 
                Pos temp ;
                temp.x1 = nx ;
                temp.y1 = ny ;
                temp.x2 = nxx ;
                temp.y2 = nyy ;
                temp.t  = now.t + 1 ;
                Q.push(temp);
                mark[nx][ny][nxx][nyy] = true ;
            }
            //人没有接触到箱子
            else
            {
                int nxx = now.x2 ;
                int nyy = now.y2 ;
                if( mark[nx][ny][nxx][nyy] == true ) continue ;
                Pos temp ;
                temp.x1 = nx ;
                temp.y1 = ny ;
                temp.x2 = nxx ;
                temp.y2 = nyy ;
                temp.t  = now.t + 1 ;
                mark[nx][ny][nxx][nyy] = true ;
                Q.push(temp);
            }  
              
        }
    }
    return -1 ;
}
 
int main()
{
    for(int i = 0 ; i < 8 ; i++)
        for(int j = 0 ; j < 8 ; j++)
            for(int k = 0 ; k < 8 ; k++)
                for(int l = 0 ; l < 8 ; l++)
                    mark[i][j][k][l] = false;
    int N , M ,px , py , qx , qy;
    cin>> N >> M ;
    for(int i = 0 ; i < N ; i++)
    {
        for(int j = 0 ; j < M ; j++)
        {
            cin >> game[i][j] ;
            //记录人的位置
            if(game[i][j] == 'X')
            {
                px = i ;
                py = j ;
            }
            //记录箱子的位置
            else if(game[i][j] == '*')
            {
                qx = i ;
                qy = j ;
            }
        }
    }
    //初始状态设置true
    mark[px][py][qx][qy] = true ;
    Pos start ;
    start.x1 = px ;
    start.y1 = py ;
    start.x2 = qx ;
    start.y2 = qy ;
    start.t  =  0 ;
    while(Q.empty() == false ) Q.pop();
    Q.push(start);
    int ans = BFS(N,M);
    cout << ans ;
    return 0 ;
}

你可能感兴趣的:(解题点滴)