1. 程式人生 > >HDOJ 1175 連連看 廣度優先搜尋

HDOJ 1175 連連看 廣度優先搜尋

Problem Description “連連看”相信很多人都玩過。沒玩過也沒關係,下面我給大家介紹一下游戲規則:在一個棋盤中,放了很多的棋子。如果某兩個相同的棋子,可以通過一條線連起來(這條線不能經過其它棋子),而且線的轉折次數不超過兩次,那麼這兩個棋子就可以在棋盤上消去。不好意思,由於我以前沒有玩過連連看,諮詢了同學的意見,連線不能從外面繞過去的,但事實上這是錯的。現在已經釀成大禍,就只能將錯就錯了,連線不能從外圍繞過。
玩家滑鼠先後點選兩塊棋子,試圖將他們消去,然後遊戲的後臺判斷這兩個方格能不能消去。現在你的任務就是寫這個後臺程式。
Input 輸入資料有多組。每組資料的第一行有兩個正整數n,m(0<n<=1000,0<m<1000),分別表示棋盤的行數與列數。在接下來的n行中,每行有m個非負整數描述棋盤的方格分佈。0表示這個位置沒有棋子,正整數表示棋子的型別。接下來的一行是一個正整數q(0<q<50),表示下面有q次詢問。在接下來的q行裡,每行有四個正整數x1,y1,x2,y2,表示詢問第x1行y1列的棋子與第x2行y2列的棋子能不能消去。n=0,m=0時,輸入結束。
注意:詢問之間無先後關係,都是針對當前狀態的!
Output 每一組輸入資料對應一行輸出。如果能消去則輸出"YES",不能則輸出"NO"。
Sample Input
3 4
1 2 3 4
0 0 0 0
4 3 2 1
4
1 1 3 4
1 1 2 4
1 1 3 3
2 1 2 4
3 4
0 1 4 3
0 2 4 1
0 0 0 0
2
1 1 2 4
1 3 2 3
0 0
Sample Output
YES
NO
NO
NO
NO
YES

    一看題目就知道是用bfs,但是要注意幾個問題:bfs是按層次進行搜尋,得到的從起點到終點的路徑(如果存在的話)是最短的。題目中說這條路徑最多隻能轉向2次,有一些情況可能得到了從起點到終點的路徑,但是它的轉向次數已經超過的2次,這樣這條路徑就不符合要求,得重新找一條。一個一般的結論:如果某一點記錄的轉向次數大於當前路徑在該點的轉向次數,那麼還能從該點再發出一條路徑來查詢。可以用一個二維陣列hash[n][n]來狀態判重,這個數組裡存的數值就是某條路徑在該點的轉向次數,if(hash[x][y]>=now.turn) q.push(now);還有個需要注意的就是能連上的點並沒有從圖中消失,所以每條查詢語句都是獨立的。(我把它當成真正的連連看來做,結果WA了10次)

#include <iostream>
#include <queue>
#include<cstdio>
using namespace std;

const int N = 1001;
bool flag;
int n,m,sx,sy,ex,ey;
int hash[N][N],map[N][N];
int dir[4][2]={{1,0},{0,1},{-1,0},{0,-1}};
struct node{
   int x,y,turn,d;
}start;
queue<node> q;

inline bool in(const node &p){
  if(p.x<0 || p.y<0 || p.x>=n || p.y>=m)
        return false;
     return true;
}
 void bfs(){
     node now,t;
    while(!q.empty()){
         now=q.front(),q.pop();
         if(now.x==ex && now.y==ey && now.turn<=2)
         {
            flag=true;
            return;
         }
         for(int i=0;i<4;i++)
         {
             if(i==(now.d+2)%4) continue ;//不能往回走
             t.x=now.x+dir[i][0],t.y=now.y+dir[i][1];
             if(now.d==i)
                 t.turn=now.turn,t.d=now.d;
             else
                 t.turn=now.turn+1,t.d=i;
             if(in(t) && (map[t.x][t.y]==0||(t.x==ex&&t.y==ey)) && hash[t.x][t.y]>=t.turn&&t.turn<=2)
                 hash[t.x][t.y]=t.turn,q.push(t);
         }
     }
 }
 int main()
 {
     int i,j,t;
     while(scanf("%d %d",&n,&m),n||m)
     {
         for(i=0;i<n;i++)
           for(j=0;j<m;j++)
           {
            scanf("%d",&map[i][j]);
           }

        scanf("%d",&t);
        while(t--)
        {
            scanf("%d %d %d %d",&sx,&sy,&ex,&ey);
            sx--,sy--,ex--,ey--;
            if((map[sx][sy]!=map[ex][ey]) || map[sx][sy]==0 || map[ex][ey]==0 || (sx==ex&&sy==ey))
            {
                 puts("NO");
                 continue;
            }
            for(i=0;i<n;i++)
               for(j=0;j<m;j++)
                  hash[i][j]=111;
            while(!q.empty()) q.pop();
            for(i=0;i<4;i++)
            {
                 start.x=sx,start.y=sy,start.turn=0,start.d=i;
                 q.push(start);
            }
            flag=false;
            //設定為0,防止繞圈
            hash[sx][sy]=0;
            bfs();
            puts(flag ? "YES":"NO");
         }
     }
     return 0;
}

一般的bfs是用來求取最短距離的,於是定好起始點,按照四個方向搜尋就可以了,每個點只是也只是允許訪問一次,但是在這個連連看當中,是可以允許重複訪問點的,因為我們要的不是距離最短的點,而是轉彎最少的線路,於是我們增加了一個變數用於儲存轉彎的次數,當這個點的轉彎的次數變得更少的時候我們就更新這個點到隊列當中去,並更新這個點的turn。於是會走很多冤枉路,耗時很長

4. 連連看

這其實是《程式設計之美》第一章”1.14 連連看遊戲設計“中介紹的一個演算法,連連看可以由一個二維陣列表示,陣列元素的值表示不同的圖形,我們可以用0表示該位置沒有圖形。  連連看中兩個結點能夠相連從而消去的標準是:相連不超過兩個彎的相同圖形:

(圖片取自程式設計之美)

這個問題的結構與迷宮問題有點類似,可是為了應用BFS,我們的大腦也得轉個彎,玩家依次點了兩個結點後,我們需要判斷是否可以消去:

  • 圖形是否相等非常簡單,只要判斷該處元素的值即可
  • 相連是否不超過兩個彎,我們需要從中取一個結點為起始點,先拿到無須轉彎就能到達的結點集合A,看目標結點是否在裡面;如果不在,則需要對結點集合A中所有結點,拿到其無須轉彎就能到達的結點(未在之前訪問過),判斷目標結點是否在內;如果不在,則繼續擴充套件,這次如果再不在,說明兩結點連線超過兩個彎了,不滿足

此處, vertex是所有沒有圖形的結點,而edge則是任意兩個在同一直線上的,未被有圖形的結點截斷的結點之間的聯絡。BFS的應用在於結點距離不超過2次,此處距離是指轉彎次數。

通過上訴例子,BFS之強大與有趣可見一斑!

2012-5-17閱讀241 評論0

思路:用bfs一次把這個方向上能到的點入隊

code:

#include <stdio.h>
int m = 0, n = 0, front = 0, rear = 0, map[1002][1002], used[1002][1002];
int X1 = 0, Y1 = 0, X2 = 0, Y2 = 0;
int dir[4][2] = {{-1, 0}, {0, 1}, {1, 0}, {0, -1}};
typedef struct
{
    int x, y, count;
}node;
node step, quene[1000*1000+1];
int bfs()
{
    int i = 0, x = 0, y = 0, fx = 0, fy = 0, count = 0;
    quene[rear++] = step;
    while(front<rear)
    {
        x = quene[front].x; y = quene[front].y; count = quene[front++].count;
        for(i = 0; i<4; i++)
        {
            fx = x+dir[i][0]; fy = y+dir[i][1];
            while(!map[fx][fy] && count<3 && fx>0 && fx<=n && fy>0 &&fy<=m && !used[fx][fy])//這方向上能到的點入隊
            {
                used[fx][fy] = 1;
                step.x = fx; step.y = fy; step.count = count+1;
                quene[rear++] = step;
                fx += dir[i][0]; fy += dir[i][1];
            }
            if(fx == X2 && fy == Y2 && count<3)
                return 1;
        }
    }
    return 0;
}
int main()
{
    int i = 0,  j= 0, q = 0;
    while(scanf("%d %d",&n, &m) , n && m)
    {
        for(i = 1; i<=n; i++)
            for(j = 1; j<=m; j++)
                scanf("%d",&map[i][j]);
        scanf("%d",&q);
        while(q--)
        {
            for(i = 1; i<=n; i++)
                for(j = 1; j<=m; j++)
                    used[i][j] = 0;
            scanf("%d %d %d %d",&X1, &Y1, &X2, &Y2);
            if((map[X1][Y1] != map[X2][Y2] || !map[X1][Y1] || !map[X2][Y2]) || (X1==X2 && Y2 == Y1))
                printf("NO\n");
            else
            {
                front = rear = 0;
                step.x = X1; step.y = Y1;step.count = 0;
                if(bfs())
                    printf("YES\n");
                else
                    printf("NO\n");
            }
        }
    }
    return 0;
}

分析起來,以上兩種演算法有兩種思想,一種是每個點按照四個方向搜尋四遍,分別記錄四遍過程中轉向次數最少的放入hash當中,如果其中有情況是結果比hash大了,說明這個方向搜尋到這個點路徑不好。

一種是按照點來的,一個點按照四個方向搜尋,搜出4個點放進去