當使用BFS遍歷一張無權圖,每次從隊列中取出隊首進行一系列擴展,將擴展成功結點放入隊尾中;
這樣的操做會使得整隊列知足「兩段性」,即對於這張搜索樹中隊列只會保留兩層的結點;ios
證實:算法
因爲是「兩段性」,而從起始點到達節點所走的最短路程爲節點所處的深度,則該隊列知足單調性;
綜上,對於普通BFS,有兩個特性:單調性 、 兩段性;數組
注意:涉及狀態時,必定要明確什麼是第一關鍵字,什麼是第二關鍵字。不然,會出現問題!!測試
const int dx[4] = {-1, 1, 0, 0}, dy[4] = {0, 0, -1, 1};
如此,在枚舉擴展狀態時能夠預處理幾維數組。spa
網址:http://poj.org/problem?id=3322code
立體推箱子是一個風靡世界的小遊戲。
遊戲地圖是一個N行M列的矩陣,每一個位置多是硬地(用」.」表示)、易碎地面(用」E」表示)、禁地(用」#」表示)、起點(用」X」表示)或終點(用」O」表示)。
你的任務是操做一個1×1×2的長方體。隊列
這個長方體在地面上有兩種放置形式,「立」在地面上(1×1的面接觸地面)或者「躺」在地面上(1×2的面接觸地面)。
在每一步操做中,能夠按上下左右四個鍵之一。
按下按鍵以後,長方體向對應的方向沿着棱滾動90度。
任意時刻,長方體不能有任何部位接觸禁地,而且不能立在易碎地面上。
字符」X」標識長方體的起始位置,地圖上可能有一個」X」或者兩個相鄰的」X」。
地圖上惟一的一個字符」O」標識目標位置。
求把長方體移動到目標位置(即立在」O」上)所須要的最少步數。
在移動過程當中,」X」和」O」標識的位置均可以看做是硬地被利用。遊戲
輸入包含多組測試用例。
對於每一個測試用例,第一行包括兩個整數N和M。
接下來N行用來描述地圖,每行包括M個字符,每一個字符表示一塊地面的具體狀態。
當輸入用例N=0,M=0時,表示輸入終止,且該用例無需考慮。字符串
每一個用例輸出一個整數表示所需的最少步數,若是無解則輸出」Impossible」。
每一個結果佔一行。get
3≤N,M≤500
7 7 ####### #..X### #..##O# #....E# #....E# #.....# ####### 0 0
10
此題對於初次接觸廣搜的讀者有些困難。
其實,若是把狀態定義好,並非很難。
定義:(x,y,lie)表示該箱子處於(x,y)的位置;lie表示該箱子的狀態:
const int next_x[3][4] = {{-2 , 1 , 0 , 0} , {-1 , 1 , 0 , 0} , {-1 , 2 , 0 , 0}}; const int next_y[3][4] = {{0 , 0 , -2 , 1} , {0 , 0 , -1 , 2} , {0 , 0 , -1 , 1}}; const int next_lie[3][4] = {{2 , 2 , 1 , 1} , {1 , 1 , 0 , 0} , {0 , 0 , 2 , 2}};
判斷該箱子滾動狀況,其中next[ lie ] [ i ]即爲當前箱子狀態爲lie,向方向i滾動狀況;
代碼以下:
#include<iostream> #include<cstring> #include<cstdio> #include<queue> #include<cmath> using namespace std; const int MAX_size = 500 + 10; const int dx[4] = {-1 , 1 , 0 , 0} , dy[4] = {0 , 0 , -1 , 1};//ud -> rl const int next_x[3][4] = {{-2 , 1 , 0 , 0} , {-1 , 1 , 0 , 0} , {-1 , 2 , 0 , 0}}; const int next_y[3][4] = {{0 , 0 , -2 , 1} , {0 , 0 , -1 , 2} , {0 , 0 , -1 , 1}}; const int next_lie[3][4] = {{2 , 2 , 1 , 1} , {1 , 1 , 0 , 0} , {0 , 0 , 2 , 2}}; struct rec { int x , y , lie; }; rec st , ed; int n , m , d[MAX_size][MAX_size][3]; queue <rec> Q; char map[MAX_size][MAX_size]; void init() { bool valid = true; int i , j; for(i = 1; i <= n; ++ i) { for(j = 1; j <= m; ++ j) { if(map[i][j] == 'O') ed.lie = 0 , ed.x = i , ed.y = j; else if(map[i][j] == 'X' && valid) { valid = false; st.x = i , st.y = j , st.lie = false; if(map[i + 1][j] == 'X') st.lie = 2; if(map[i][j + 1] == 'X') st.lie = 1; } } } return; } bool valid(int x , int y) { if(x < 1 || x > n || y < 1 || y > m) return false; return true; } bool valid(int x , int y , int lie) { if(map[x][y] == '#') return false; if(!valid(x , y)) return false; if(lie == 0 && map[x][y] == 'E') return false; if(lie == 1 && map[x][y + 1] == '#') return false; if(lie == 2 && map[x + 1][y] == '#') return false; return true; } int bfs() { int x , y , lie; while(!Q.empty()) Q.pop(); Q.push(rec {st.x , st.y , st.lie}); d[st.x][st.y][st.lie] = 0; while(!Q.empty()) { rec now = Q.front(); Q.pop(); // up down left right for(int i = 0; i < 4; ++ i) { x = now.x + next_x[now.lie][i]; y = now.y + next_y[now.lie][i]; lie = next_lie[now.lie][i]; if(d[x][y][lie] != -1) continue; if(!valid(x , y , lie)) continue; d[x][y][lie] = d[now.x][now.y][now.lie] + 1; Q.push(rec {x , y , lie}); if(x == ed.x && y == ed.y && lie == ed.lie)return d[x][y][lie]; } } return -1; } int main() { while(scanf("%d %d" , &n , &m) == 2 && n && m) { memset(d , -1 , sizeof(d)); memset(map , ' ' , sizeof(map)); for(int i = 1; i <= n; ++ i) scanf("%s" , (map[i] + 1) ); init(); int ans = bfs(); if(ans != -1) printf("%d\n" , ans); else printf("Impossible\n"); } return 0; }
網址:http://noi-test.zzstep.com/contest/0x29「搜索」練習/2906 武士風度的牛
農民John有不少牛,他想交易其中一頭被Don稱爲The Knight的牛。
這頭牛有一個獨一無二的超能力,在農場裏像Knight同樣地跳(就是咱們熟悉的象棋中馬的走法)。
雖然這頭神奇的牛不能跳到樹上和石頭上,可是它能夠在牧場上隨意跳,咱們把牧場用一個x,y的座標圖來表示。
這頭神奇的牛像其它牛同樣喜歡吃草,給你一張地圖,上面標註了The Knight的開始位置,樹、灌木、石頭以及其它障礙的位置,除此以外還有一捆草。
如今你的任務是,肯定The Knight要想吃到草,至少須要跳多少次。
The Knight的位置用’K’來標記,障礙的位置用’*’來標記,草的位置用’H’來標記。
這裏有一個地圖的例子:
11 | . . . . . . . . . . 10 | . . . . * . . . . . 9 | . . . . . . . . . . 8 | . . . * . * . . . . 7 | . . . . . . . * . . 6 | . . * . . * . . . H 5 | * . . . . . . . . . 4 | . . . * . . . * . . 3 | . K . . . . . . . . 2 | . . . * . . . . . * 1 | . . * . . . . * . . 0 ---------------------- 1 0 1 2 3 4 5 6 7 8 9 0
The Knight 能夠按照下圖中的A,B,C,D…這條路徑用5次跳到草的地方(有可能其它路線的長度也是5):
11 | . . . . . . . . . . 10 | . . . . * . . . . . 9 | . . . . . . . . . . 8 | . . . * . * . . . . 7 | . . . . . . . * . . 6 | . . * . . * . . . F< 5 | * . B . . . . . . . 4 | . . . * C . . * E . 3 | .>A . . . . D . . . 2 | . . . * . . . . . * 1 | . . * . . . . * . . 0 ---------------------- 1 0 1 2 3 4 5 6 7 8 9 0
注意: 數據保證必定有解。
第1行: 兩個數,表示農場的列數C(C<=150)和行數R(R<=150)。
第2..R+1行: 每行一個由C個字符組成的字符串,共同描繪出牧場地圖。
一個整數,表示跳躍的最小次數。
10 11 .......... ....*..... .......... ...*.*.... .......*.. ..*..*...H *......... ...*...*.. .K........ ...*.....* ..*....*..
5
此題相對於上一道比較簡單。
代碼 :
#include<iostream> #include<cstring> #include<cstdio> #include<cmath> #include<queue> #define pa pair <int, int> using namespace std; struct rec { int x, y; } st , ed; const int MAX_size = 160 + 5; const int dx[8] = {-2, -1, 1, 2, 2, 1, -1, -2}; const int dy[8] = {1, 2, 2, 1, -1, -2, -2, -1}; char map[MAX_size][MAX_size]; int n, m; int d[MAX_size][MAX_size]; bool book[MAX_size][MAX_size]; void init() { memset(d, 0, sizeof(d)); memset(book, false, sizeof(book)); for(int i = 1; i <= n; ++ i) { for(int j = 1; j <= m; ++ j) { if(map[i][j] == 'K') { map[i][j] = '.'; st.x = i, st.y = j; continue; } if(map[i][j] == 'H') { map[i][j] = '.'; ed.x = i, ed.y = j; continue; } } } return; } bool valid(rec next) { if(next.x < 1 || next.x > n || next.y < 1 || next.y > m) return 0; if(map[next.x][next.y] == '.' && book[next.x][next.y] == false) return 1; return 0; } int bfs() { queue <rec> Q; while(!Q.empty()) Q.pop(); book[st.x][st.y] = true; Q.push(st); while(!Q.empty()) { rec now = Q.front(), next; Q.pop(); for(int i = 0; i < 8; ++ i) { next.x = now.x + dx[i]; next.y = now.y + dy[i]; if(valid(next)) { d[next.x][next.y] = d[now.x][now.y] + 1; if(next.x == ed.x && next.y == ed.y) return d[ed.x][ed.y]; book[next.x][next.y] = true; Q.push(next); } } } return -1; } int main() { scanf("%d%d", &m, &n); for(int i = 1; i <= n; ++ i) scanf("%s", map[i] + 1); init(); if(st.x == ed.x && st.y == ed.y) puts("0"); else printf("%d\n", bfs()); return 0; }
網址:http://noi-test.zzstep.com/contest/0x20「搜索」例題/2501 矩陣距離
給定一個N行M列的01矩陣A,A[i][j] 與 A[k][l] 之間的曼哈頓距離定義爲:
dist(A[i][j],A[k][l])=|i−k|+|j−l|
輸出一個N行M列的整數矩陣B,其中:
B[i][j]=min1≤x≤N,1≤y≤M,A[x][y]=1dist(A[i][j],A[x][y])
第一行兩個整數n,m。
接下來一個N行M列的01矩陣,數字之間沒有空格。
一個N行M列的矩陣B,相鄰兩個整數之間用一個空格隔開。
1≤N,M≤1000
3 4 0001 0011 0110
3 2 1 0 2 1 0 0 1 0 0 1
這就是經典的Flood-fill,就像灑一地水,看能淹了多大的地方。這道題最開始只需將每個位置爲1的座標放進隊列,進行BFS,每次擴展輪數即爲該輪數下位置的最短的B[i][j]值。正確性顯然。
代碼以下:
#include<iostream> #include<cstring> #include<cstdio> #include<bitset> #include<queue> #include<cmath> #define pii pair <int, int> using namespace std; queue <pii> Q; const int MAX_size = 1000 + 5; const int dx[4] = {-1, 1, 0, 0}; const int dy[4] = {0, 0, -1, 1}; bitset <MAX_size> book[MAX_size]; int n, m, map[MAX_size][MAX_size]; int d[MAX_size][MAX_size]; void bfs() { memset(d, 0, sizeof(d)); while(!Q.empty()) { pii now = Q.front(); Q.pop(); int x, y; for(int i = 0; i < 4; ++ i) { x = now.first + dx[i], y = now.second + dy[i]; if(x < 1 || x > n || y < 1 || y > m) continue; if(!book[x][y] && map[x][y] == 0) { book[x][y] = true; d[x][y] = d[now.first][now.second] + 1; Q.push(make_pair(x, y)); } } } return; } int main() { while(!Q.empty()) Q.pop(); scanf("%d %d", &n, &m); for(int i = 1; i <= n; ++ i) { for(int j = 1; j <= m; ++ j) { scanf("%1d", &map[i][j]); if(map[i][j] == 1) Q.push(make_pair(i, j)); } book[i].reset(); } bfs(); for(int i = 1; i <= n; ++ i) { for(int j = 1; j <= m; ++ j) printf("%d ", d[i][j]); puts(""); } return 0; }
練習:乳草的入侵
網址:http://noi-test.zzstep.com/contest/0x29「搜索」練習/2907 乳草的入侵
草地像往常同樣,被分割成一個高度爲Y, 寬度爲X的直角網格。
(1,1)是左下角的格(也就是說座標排布跟通常的X,Y座標相同)。
乳草一開始佔領了格(Mx,My)。
每一個星期,乳草傳播到已被乳草佔領的格子四面八方的每個沒有不少石頭的格(包括垂直與水平相鄰的和對角線上相鄰的格)內。
1周以後,這些新佔領的格又能夠把乳草傳播到更多的格里面了。
達達想要在草地被乳草徹底佔領以前儘量的享用全部的牧草。
她很好奇到底乳草要多久才能佔領整個草地。
若是乳草在0時刻處於格(Mx,My),那麼幾個星期之後它們能夠徹底佔領入侵整片草地呢(對給定的數據老是會發生)?
在草地地圖中,」.」表示草,而」*」
表示大石。
好比這個X=4, Y=3的例子。
.... ..*. .**.
若是乳草一開始在左下角(第1排,第1列),那麼草地的地圖將會以以下態勢發展:
.... .... MMM. MMMM MMMM ..*. MM*. MM*. MM*M MM*M M**. M**. M**. M**. M**M 星期數 0 1 2 3 4
乳草會在4星期後佔領整片土地。
第1行: 四個由空格隔開的整數: X, Y, Mx, My
第2到第Y+1行: 每行包含一個由X個字符(」.」表示草地,」*」表示大石)構成的字符串,共同描繪了草地的完整地圖。
輸出一個整數,表示乳草徹底佔領草地所須要的星期數。
1≤X,Y≤100
4 3 1 1 .... ..*. .**.
41
這道題也同樣,由此觀之,flood-fill實際上就是在一個圖上的廣度優先搜索之擴展,其算法本質跟廣搜差很少。
代碼實現:
#include<iostream> #include<cstring> #include<cstdio> #include<queue> #include<cmath> #define maxn 100 + 5 using namespace std; struct rec { int x, y; }; const int dx[8] = {-1, -1, 0, 1, 1, 1, 0, -1}; const int dy[8] = {0, 1, 1, 1, 0, -1, -1, -1}; int d[maxn][maxn]; char map[maxn][maxn]; int X, Y, Mx, My, cnt = 0; bool valid(rec next) { if(next.x < 1 || next.x > Y || next.y < 1 || next.y > X) return false; if(d[next.x][next.y] != -1) return false; return map[next.x][next.y] == '.'; } int bfs() { queue <rec> Q; while(!Q.empty()) Q.pop(); d[My][Mx] = 0; Q.push(rec {My, Mx}); int ans = 0; while(!Q.empty()) { rec now = Q.front(), next; Q.pop(); for(int i = 0; i < 8; ++ i) { next.x = now.x + dx[i]; next.y = now.y + dy[i]; if(valid(next)) { d[next.x][next.y] = d[now.x][now.y] + 1; Q.push(next); ans = max(ans, d[next.x][next.y]); } } } return ans; } int main() { scanf("%d %d %d %d" , &X , &Y , &Mx , &My); for(int y = Y; y > 0; -- y) scanf("%s", map[y] + 1); memset(d, -1, sizeof(d)); printf("%d\n", bfs()); return 0; }