1. 程式人生 > >洛谷P2346四子連棋

洛谷P2346四子連棋

題目描述

在一個4*4的棋盤上擺放了14顆棋子,其中有7顆白色棋子,7顆黑色棋子,有兩個空白地帶,任何一顆黑白棋子都可以向上下左右四個方向移動到相鄰的空格,這叫行棋一步.

黑白雙方交替走棋任意一方可以先走如果某個時刻使得任意一種顏色的棋子形成四個一線(包括斜線),這樣的狀態為目標棋局。

輸入輸出格式

輸入格式:

從檔案中讀入一個4*4的初始棋局,黑棋子用B表示,白棋子用W表示,空格地帶用O表示。

輸出格式:

用最少的步數移動到目標棋局的步數。

輸入輸出樣例:

輸入:

BWBO

WBWB

BWBW

WBWO

輸出:
5

 分析:
先講講我做這道題的悲慘經歷,因為我太弱了想了半小時然後寫一下卡一下的寫出個BFS然後果不其然樣例都沒過,

然後換了種思路重寫了一遍然後在機房3位dalao歷經1小時的debug最後終於A了這道題,十分感謝這3位dalao.

接下來進入正題.

思路:
首先這是一道搜尋加模擬題這道題是真的要我這個蒟蒻的命,先分析題目由得知我們有一個4*4的棋盤其中有兩個字母為“O”的空白

然後在棋盤中其他字母為B(Black)或W(White),然後我們需要像走五子棋一樣要麼先走白要麼先走黑當出現橫排出現4格連續的“B”或“W或”豎列出現4格連續的“B”或“W”

亦或是斜著(包含左斜,右斜)出現4格連續的“B”或“W”只要出現其中一種情況就達成了目標情況(就像五子棋一樣).

輸出最小步數.                     

方法:
因為是最小步數我們首先考慮BFS顯然這道題BFS是行的通的,但是有一點需要注意玩過五子棋都知道黑白誰先手結果可能不一樣,因此我們需要跑兩邊BFS(1.白先手時2.黑先手時)

然後我們需要下一個棋盤的狀況,我們可以用到和“八數碼難題”一樣的更新操作即我們可以先將每個矩陣壓成一個字串當需要判斷是否成立目標狀況時在變回矩陣判斷,

壓縮操作:

為什麼要這樣做,因為每次Map都在變而我們必須要存下每個不同的狀況所以我們需要字串存下每個不同狀況.

 

void compression()
{
    change.clear();//在壓縮成一個字串時當然需要將這個字串清空
for(int i=1;i<=4;i++)//4*4的矩陣 for(int j=1;j<=4;j++) change.push_back(Map[i][j]); }

 

既然有壓縮操作那就必須有拆分的操作

拆分操作:
為什麼要這麼做,由上面的壓縮操作可以得到一個全新的棋盤狀況但是我們需要判斷當前的狀況是否符合標準狀況的條件但是很顯然字串是無法判斷的

所以我們需要將這個字串再拆分成一個全新的矩陣再用這個矩陣判斷是否符合情況

void break_up(string a)
{
    posx=1;posy=1;
    for(int i=0;i<a.length();i++)
    {
        if(posy==5)
        {
            posy=1;
            posx++;
        }
        Map[posx][posy]=a[i];
        posy++;
    }
}

 

上面都提到了判斷操作

相信判斷操作大家都會這裡就不多做解釋了

判斷操作:

當符合情況就返回true否則就返回false

可以發現check裡我們用的是string w 那就說明再判斷前我們一定要將需要判斷的字串拆分成矩陣

bool check(string w)
{
    for(int i=1;i<=4;i++)//橫豎
    {
        if(Map[i][1]==Map[i][2]&&Map[i][1]==Map[i][3]&&Map[i][1]==Map[i][4]) return true;
        if(Map[1][i]==Map[2][i]&&Map[1][i]==Map[3][i]&&Map[1][i]==Map[4][i]) return true;
    }
    if(Map[1][4]==Map[2][3]&&Map[1][4]==Map[3][2]&&Map[1][4]==Map[4][1]) return true;//右斜
    if(Map[1][1]==Map[2][2]&&Map[1][1]==Map[3][3]&&Map[1][1]==Map[4][4]) return true;//左斜
    return false;
}

在貼整段程式碼之前我還要再BB一兩句下面這些話可能對你理解這題的BFS有幫助

1.由題意我們可以知道這題的BFS其實就是將“O”這個點去更新整個棋盤的所有點,我用的是swap這個就是將兩個點交換位置至於BFS怎麼寫看大家的習慣

但是要用swap的小夥伴要注意一下,當搜尋不滿足情況時因為我們已經交換這兩個點所以我們必須再交換回去才能continue(即再swap一次)

2.有題意我們可以得知這題有兩個空格“O”誰先走都沒有規定因此我們必須寫兩個for迴圈這裡也許需要思考一下為什麼.

3.再有題意可得這是一個像五子棋一樣的遊戲所以我們必須一黑一白交替走棋(至於誰先我前面已經說了我們要注意這兩種情況因為情況可能不同)

由此三點我們可以得出我們的結構體裡需要儲存的東西

struct Node 
{
    int x1,y1,x2,y2,t;//x1,y1為第一個空格“O”的位置    x2,y2為第二個“O”的位置
    bool order;//藉此判斷走棋順序
    string s;//儲存每一個矩陣壓成的字串
}; 

接下來我會貼出整段程式碼:
如果還有點懵的可以看看整段程式碼讓你的思路更清晰

程式碼:

 

#include <iostream>
#include <cstring>
#include <cmath>
#include <string>
#include <queue>
#include <map>
using namespace std;
const int INF=0x3f3f3f3f;
char Map[5][5],tmp[5][5];
int startx1,starty1,startx2,starty2,posx,posy,ans=INF;
map<string,bool> vis;
string x,change; 
bool flag;
int dirx[4]={0,1,-1,0};
int diry[4]={1,0,0,-1};
struct Node 
{
    int x1,y1,x2,y2,t;//這些變數名我在上面解釋的很清楚 
    bool order;
    string s;
}; 
void compression()//壓縮操作 
{
    change.clear();
    for(int i=1;i<=4;i++)
    for(int j=1;j<=4;j++)
    change.push_back(Map[i][j]);
}
void break_up(string a)//拆分操作 
{
    posx=1;posy=1;
    for(int i=0;i<a.length();i++)
    {
        if(posy==5)
        {
            posy=1;
            posx++;
        }
        Map[posx][posy]=a[i];
        posy++;
    }
}
bool check(string w)//判斷是否成立 
{
    for(int i=1;i<=4;i++)
    {
        if(Map[i][1]==Map[i][2]&&Map[i][1]==Map[i][3]&&Map[i][1]==Map[i][4]) return true;
        if(Map[1][i]==Map[2][i]&&Map[1][i]==Map[3][i]&&Map[1][i]==Map[4][i]) return true;
    }
    if(Map[1][4]==Map[2][3]&&Map[1][4]==Map[3][2]&&Map[1][4]==Map[4][1]) return true;
    if(Map[1][1]==Map[2][2]&&Map[1][1]==Map[3][3]&&Map[1][1]==Map[4][4]) return true;
    return false;
}
void bfs(bool c)
{
    vis[x]=true;
    queue<struct Node> que;
    while(!que.empty())//因為我們要進行第二次BFS所以有可能第一次的BFS的佇列並沒有彈空 
    {//所以進行第二次BFS時我們必須將佇列彈空 
        que.pop();
    }
    struct Node now;
    now.x1=startx1; now.y1=starty1; now.x2=startx2; now.y2=starty2; now.t=0; now.s=x; now.order=c;
    que.push(now);
    while(!que.empty())
    {
        now=que.front();
        
        break_up(now.s);//更新矩陣
        /*
        注意這個時候雖然我們的字串更新了
        但是我們的矩陣並未更新,如果不更新矩陣就判斷是否成立的話那我們判斷的就是上一個矩陣的狀態這毫無疑問是錯誤的 
        */
        if(check(now.s))//判斷矩陣而不是字串,就如上面說的一樣 
        {
            ans=min(ans,now.t);
            return;
        }
        
        que.pop();
        for(int i=0;i<4;i++)
        {
            int xx=now.x1+dirx[i];
            int yy=now.y1+diry[i];
            if(xx<1||xx>4||yy<1||yy>4) continue;
            swap(Map[now.x1][now.y1],Map[xx][yy]);
            if(now.order&&Map[now.x1][now.y1]=='B')//該走白子時走黑子的情況 這是不合法的 
            {
                swap(Map[xx][yy],Map[now.x1][now.y1]);//不符合情況時我們必須swap回到上一個狀態
                continue;
            } 
            if(!now.order&&Map[now.x1][now.y1]=='W')//該走黑子時走白子的情況 這是不合法的 
            {
                swap(Map[xx][yy],Map[now.x1][now.y1]);//不符合情況時我們必須swap回到上一個狀態
                continue;
            }
            compression();//更新字串(將更新的矩陣再壓縮成一個新的字串)
            if(vis[change])//判斷這個字串是否用過 
            {
                swap(Map[xx][yy],Map[now.x1][now.y1]);//我前面應該講了swap當不符合情況時我們必須swap回到上一個狀態 
                continue;
            }
            vis[change]=true;
            swap(Map[xx][yy],Map[now.x1][now.y1]);//當然需要swap回到上一個情況因為我們必須保證更新這個矩陣所有能行的情況時這個矩陣的Map不能改變 
            struct Node next;
            next.x1=xx;next.y1=yy;next.t=now.t+1;next.s=change;next.order=!now.order;next.x2=now.x2;next.y2=now.y2;//next.order=!now.order因為下次必須走和這次顏色不同的棋子 
            que.push(next); //next.x2=now.x2 next.y2=now.y2  因為這裡是第一個空格的情況所以第二個空格的座標並未改變 
        }
        for(int i=0;i<4;i++)
        {
            int xx=dirx[i]+now.x2;
            int yy=diry[i]+now.y2;
            if(xx<1||xx>4||yy<1||yy>4) continue;
            swap(Map[now.x2][now.y2],Map[xx][yy]);
            if(now.order&&Map[now.x2][now.y2]=='B')//該走白子時走黑子的情況 這是不合法的 
            {
                swap(Map[xx][yy],Map[now.x2][now.y2]);//不符合情況時我們必須swap回到上一個狀態 
                continue;
            }
            if(!now.order&&Map[now.x2][now.y2]=='W')//該走黑子時走白子的情況 這是不合法的 
            {
                swap(Map[xx][yy],Map[now.x2][now.y2]);//不符合情況時我們必須swap回到上一個狀態 
                continue;
            }
            compression();//更新字串(將更新的矩陣再壓縮成一個新的字串)
            if(vis[change])//判斷這個字串是否用過 
            {
                swap(Map[xx][yy],Map[now.x2][now.y2]);//我前面應該講了swap當不符合情況時我們必須swap回到上一個狀態 
                continue;
            }
            vis[change]=true;
            swap(Map[xx][yy],Map[now.x2][now.y2]);//當然需要swap回到上一個情況因為我們必須保證更新這個矩陣所有能行的情況時這個矩陣的Map不能改變
            struct Node next;
            next.x2=xx;next.y2=yy;next.t=now.t+1;next.order=!now.order;next.s=change;next.x1=now.x1;next.y1=now.y1;//next.order=!now.order因為下次必須走和這次顏色不同的棋子 
            que.push(next);//next.x1=now.x1 next.y1=now.y1  因為這裡是第二個空格的情況所以第一個空格的座標並未改變 
        }
    }
    return; 
}
int main()
{
    for(int i=1;i<=4;i++)
        for(int j=1;j<=4;j++)
            cin>>Map[i][j];
    for(int i=1;i<=4;i++)//我們需要字串x儲存最原始的棋盤因為我們要跑兩次BFS白棋先走和黑棋先走的兩種情況 
    for(int j=1;j<=4;j++)
    x.push_back(Map[i][j]);
    for(int i=1;i<=4;i++)//找到兩個空格的座標 
    {
        for(int j=1;j<=4;j++)
        {
            if(Map[i][j]=='O'&&!flag)
            {
                startx1=i;starty1=j;//startx1 starty1 第一個空格 
                flag=true;
            }
            else if(Map[i][j]=='O'&&flag)
            {
                startx2=i;starty2=j;//startx2  starty2 第二個空格 
            }
        }
    }
    bfs(true);//true 是白棋先走的情況 
    bfs(false);//false 是黑棋先走的情況 
    if(ans==0)//這裡為當我們輸入的是符合情況 
    cout<<1;//我用的luogu的oj不知道為什麼當輸入的是符合情況時答案是1所以我們這裡特判一下就可以了 
    else 
    cout<<ans;
    return 0;
}