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 <stdio.h>
#include <queue>
using namespace std;

bool mark[50][50][50] ;
int maze[50][50][50] ;
struct Pos
{
	int x,y,z ;
	int t ;
};
queue<Pos> 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 <iostream>
#include <queue>
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<Pos> 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 ;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章