1. 程式人生 > >【NOJ1044】【分支限界法】獨輪車

【NOJ1044】【分支限界法】獨輪車

1044.獨輪車

時限:1000ms 記憶體限制:10000K  總時限:3000ms

描述

獨輪車的輪子上有紅、黃、藍、白、綠(依順時針序)5種顏色

在一個如下圖所示的20*20的迷宮內每走一個格子,輪子上的顏色變化一次(原地轉向則不變色

獨輪車只能向前推或在原地轉向。每走一格或原地轉向90度均消耗一個單位時間。

現給定一個起點(S)和一個終點(T),求獨輪車以輪子上的指定顏色(未指定車頭朝向)到達終點所需的最短時間。

輸入

本題包含一個測例。

測例中分別用一個大寫字母表示方向和輪子的顏色,其對應關係為:E-東、S-南、W-西、N-北;R-紅、Y-黃、B-藍、W-白、G-綠。

在測試資料的第一行有以空格分隔的兩個整數和兩個

大寫字母,分別表示起點的座標S(x,y)、輪子的顏色和開始的方向,

第二行有以空格分隔的兩個整數和一個大寫字母,表示終點的座標T(x,y)和到達終點時輪子的顏色(不指定結束時方向)

從第三行開始的20行每行內包含20個字元,表示迷宮的狀態。其中'X'表示建築物,'.'表示路.

輸出

在單獨的一行內輸出一個整數,即滿足題目要求的最短時間。

#include <iostream>
#include <queue>

using namespace std;

/******************全域性變數***************************/

char maze[21][21];  //存放迷宮

struct node         //記錄獨輪車的狀態
{
    int x;      //第x行,從1開始
    int y;      //第y列,從1開始
    int color;  //車輪顏色,0==紅,1==黃,2==藍,3==白,4==旅
    int dire;   //車頭朝向,0==西,1==南,2==東,3==北
};

node start,target;  //起始狀態,末狀態

queue <node> state; //廣搜所用佇列

int used[21][21][5][4];     //此狀態是否到達過
int time[21][21][5][4];     //到達此狀態所用的時間

/******************全域性變數***************************/


/******************函式宣告***************************/

void input();   //輸入資料所用函式
node inputState(int x, int y, char c, char d);   //輸入初始狀態/末狀態函式

bool bfs();     //廣搜,成功返回true

bool canmoveto(node n1); //判斷n1狀態能否向前走一格
node moveto(node n1);    //返回n1狀態向前走一格後達到的狀態(同時更新used陣列和time陣列)

bool canturnto(node n1, int i);     //判斷n1狀態能否向i方向原地轉向,i=0表示逆時針九十度,i=1順時針九十度
node turnto(node n1, int i);        //返回n1狀態向i方向轉向後到達的狀態(同時更新used陣列和time陣列)

bool ftarget(node n1);          //判斷n1狀態是不是目標狀態
/******************函式宣告***************************/

int main()
{
    input();    //輸入資料

    if(bfs())   //廣搜
    {
        //輸出從初始狀態到目標狀態所用時間,因為目標狀態只指定了座標和顏色,
        //與“車頭朝向”無關,因此設車頭朝向為西,即target.dire=0
        cout<<time[target.x][target.y][target.color][0]<<endl;
    }                                                           
    else
    {
        cout<<"error"<<endl;
    }

    return 0;
}

/***********以下均為函式實現***************************/

void input()    //輸入資料
{
    int x,y;
    char c,d;

    //輸入初始狀態
    cin>>x>>y>>c>>d;
    start=inputState(x,y,c,d);

    //輸入目標狀態
    cin>>x>>y>>c;
    target=inputState(x,y,c,'W');    //末狀態的dire不重要,因此設為西

    //輸入地圖
    for(int i=1; i<21; i++)
    {
        for(int j=1; j<21; j++)
        {
            cin>>maze[i][j];
        }
    }
}

node inputState(int x, int y, char c, char d)    //輸入一個狀態
{
    node n1;
    n1.x=x;
    n1.y=y;
    switch(c)   //輸入顏色,將字元轉化為數字
    {
        case 'R':n1.color=0;break;
        case 'Y':n1.color=1;break;
        case 'B':n1.color=2;break;
        case 'W':n1.color=3;break;
        case 'G':n1.color=4;break;
    }
    switch(d)   //輸入車頭朝向,將字元轉化為數字
    {
        case 'W':n1.dire=0;break;
        case 'S':n1.dire=1;break;
        case 'E':n1.dire=2;break;
        case 'N':n1.dire=3;break;
    }

    return n1;
}

bool bfs()      //核心演算法,廣搜
{
    //將初始狀態入隊
    state.push(start); 

    //更新used陣列:標記此狀態已到達
    used[start.x][start.y][start.color][start.dire]=1; 

    //更新time陣列:起點到起點所用時間為0 
    time[start.x][start.y][start.color][start.dire]=0;  

    //開始搜尋
    node top;
    while(!state.empty())
    {
        top=state.front();  //取隊首元素
        state.pop();

        //嘗試向前走一格
        if(canmoveto(top))  //如果top狀態能夠往前走一格
        {
            node next=moveto(top);  //獲得前進一格之後的狀態next
            state.push(next);   //將next狀態入隊
            if(ftarget(next))   //如果next狀態就是target狀態,退出搜尋
            {
                return true;
            }
        }
        //else{cout<<"不能走"<<top.x<<' '<<top.y<<' '<<top.dire<<endl;}

        //嘗試原地轉向
        for(int i=0; i<2; i++)      //i=0表示逆時針轉九十度,i=1表示順時針轉九十度
        {
            if(canturnto(top, i))   //如果top狀態能夠向i方向轉九十度
            {
                node next=turnto(top, i);   //獲得轉向之後的狀態next
                state.push(next);   //將next狀態入隊
                if(ftarget(next))   //如果next狀態就是target狀態,退出搜尋
                {
                    return true;
                }
            }
        }
    }

    return false;   //如果佇列為空,仍未搜到目標狀態,返回false
}

//判斷身處狀態n1,能否向“前”走一格,“前”就是車頭朝向
//不可走條件:越界、前面是牆、狀態重複
bool canmoveto(node n1)
{
    switch(n1.dire)    //根據車頭朝向,判斷應朝哪個方向走一格
    {
        case 0: //W 西(左)
        {
            if(n1.y-1<1 //越界
                ||maze[n1.x][n1.y-1]=='X'  //或前方是牆
                ||used[n1.x][n1.y-1][(n1.color+1)%5][n1.dire]==1)    //或狀態重複
            {
                return false;
            }
            break;
        }
        case 1: //N 南(下)
        {
            if(n1.x+1>20 //越界
                ||maze[n1.x+1][n1.y]=='X'  //或前方是牆
                ||used[n1.x+1][n1.y][(n1.color+1)%5][n1.dire]==1)    //或狀態重複
            {
                return false;
            }
            break;
        }
        case 2: //E 東(右)
        {
            if(n1.y+1>20 //越界
                ||maze[n1.x][n1.y+1]=='X'  //或前方是牆
                ||used[n1.x][n1.y+1][(n1.color+1)%5][n1.dire]==1)    //或狀態重複
            {
                return false;
            }
            break;
        }
        case 3: //B 北(上)
        {
            if(n1.x-1<1 //越界
                ||maze[n1.x-1][n1.y]=='X'  //或前方是牆
                ||used[n1.x-1][n1.y][(n1.color+1)%5][n1.dire]==1)    //或狀態重複
            {
                return false;
            }
            break;
        }
    }
    return true;
}

//返回身處n1,向前走一格後達到的狀態n2(並更新used陣列和time陣列)
node moveto(node n1)
{
    node n2;
    n2.dire=n1.dire;   //向前走一格後,車頭朝向依然不變
    n2.color=(n1.color+1)%5;   //顏色向前輪轉一個,防止向上溢位

    switch(n1.dire)    //根據車頭朝向,判斷前進一格後的橫縱座標
    {
        case 0: //西(左)
        {
            n2.x=n1.x;
            n2.y=n1.y-1;    //面向西走一格
            break;
        }
        case 1: //南(下)
        {
            n2.x=n1.x+1;
            n2.y=n1.y;
            break;
        }
        case 2: //東(右)
        {
            n2.x=n1.x;
            n2.y=n1.y+1;
            break;
        }
        case 3: //北(上)
        {
            n2.x=n1.x-1;
            n2.y=n1.y;
            break;
        }
    }

    //更新used陣列,標記n2狀態已到達過
    used[n2.x][n2.y][n2.color][n2.dire]=1;

    //更新time陣列,到達n2狀態,比到達n1狀態多用了一個時間
    time[n2.x][n2.y][n2.color][n2.dire] = 1 + time[n1.x][n1.y][n1.color][n1.dire];

    return n2;
}

//判斷能否向i方向原地轉向,0==逆時針九十度,1==順時針九十度
//不可轉條件:狀態重複
bool canturnto(node n1, int i)
{
    if(i==0)    //逆時針轉九十度
    {
        if(used[n1.x][n1.y][n1.color][(n1.dire+1)%4]==1)
        {
            return false;
        }
    }
    else
    {
        if(used[n1.x][n1.y][n1.color][(n1.dire+4-1)%4]==1)
        {
            return false;
        }
    }
    return true;
}

//返回身處n1,向i方向轉九十度後到達的狀態n2(並更新used陣列和time陣列)
node turnto(node n1, int i)
{
    node n2;
    n2.x=n1.x;  //原地轉向,位置不變
    n2.y=n1.y;
    n2.color=n1.color;  //原地轉向,顏色不變

    if(i==0)    //逆時針轉九十度
    {
        n2.dire=(n1.dire+1)%4;      //更新車頭朝向,防止向上溢位
    }
    else        //順時針轉九十度
    {
        n2.dire=(n1.dire+4-1)%4;    //更新車頭朝向,防止向下溢位
    }

    //更新used陣列,標記n2狀態已到達
    used[n2.x][n2.y][n2.color][n2.dire]=1;

    //更新time陣列,從起點到達n2狀態,比從起點到n1狀態,多用了一個時間
    time[n2.x][n2.y][n2.color][n2.dire] = 1 + time[n1.x][n1.y][n1.color][n1.dire];

    return n2;
}

//判斷n1是否到達目標狀態
bool ftarget(node n1)
{
    //目標狀態的dire不重要,故不加入判斷條件
    if(n1.x==target.x&&n1.y==target.y&&n1.color==target.color)  //若到達目標狀態
    {
        //將從起點到達狀態n1(dire=任意)的時間,賦值給我們指定的目標狀態target(dire=0)
        time[target.x][target.y][target.color][0]=time[n1.x][n1.y][n1.color][n1.dire];  
        return true;                                                                    
    }
    else
    {
        return false;
    }
}

【後記】

1.第一次感覺程式碼寫的心力交瘁……累skr人……大概也不會有人看這麼又臭又長的程式碼叭……