UVA 11624 Fire!
阿新 • • 發佈:2020-12-09
題目如下:
喬在迷宮中工作。不幸的是,迷宮的一部分著火了,迷宮的主人沒有制定火災的逃跑計劃。請幫助喬逃離迷宮。根據喬在迷宮中的位置以及迷宮的哪個方塊著火,你必須確定火焰燒到他之前,喬是否可以離開迷宮,如果能離開他能跑多快。
喬和火每分鐘移動一個方格,上、下、左、右,四個方向中的一個。火勢向四個方向同時蔓延。喬可以從迷宮的任何一個邊界逃離迷宮。無論是喬還是火都不會到達有牆的位置。
輸入
第一行輸入包含一個整數,即測試次數
每個測試用例的第一行包含兩個
整數R和C,用空格分隔,1≤R,C≤1000
下面R行中,每一行都包含C個字元,以及每個字元是以下之一:
# 代表牆
. 代表空地,火和喬是可通行的
J 喬在迷宮中最初的位置,火和喬是可通行的
F 代表火
在每組測試中只有一個J
輸出
對於每個測試用例,如果在火蔓延的時候燒到了喬,則喬無法逃出迷宮,輸出'IMPOSSIBLE'如果喬能逃出迷宮,則輸出喬最快可以在幾分鐘內安全逃出迷宮,每組輸出佔一行
樣例輸入
2
4 4
#JF#
#..#
#..#
3 3
#J.
#.F
樣例輸出
3
IMPOSSIBLE
解題思路:
首先,這道題跟之前所做所有bfs問題的遍歷方式不一樣,之前所做的問題都是遍歷四個方向後(這裡以四個方向舉例)從四個方向(A B C D)中選取一個方向A,之後按照這個方向A再次遍歷4個方向,遍歷完之後,捨棄方向A,去遍歷方向B,之後以此類推。但是,這道題是從起始點開始遍歷四個方向(A B C D) 後,將4個方向(A,B,C,D)同時進行遍歷(這時就會有16個方向)。然後以此類推。因此,我們就不能按照原先的模板來書寫bfs了。因為我們要將所有在佇列中的點都要去遍歷一遍。因此為了達到這樣的效果,我們可以事先去計算佇列中的長度,之後根據佇列中的長度來寫一個for迴圈,這樣的話就會保證佇列中所有的點都會被遍歷一遍。(在這道題中人和火都是這樣的遍歷方式)
其次,在這道題中火不只有一個,可能會有多個的存在。並且,在這道題中由於人逃出邊界就算勝利,因此我們要考慮下標的情況。不能讓下標出現負數,否則的話就會RE!因此,我們選擇下標為(1-R,1-C)而不是(0-R-1,0-R-1)!
判斷逃出邊界的方式共有兩種,第一種是當人的x座標等於1或者R時,y座標等於1或者C時,就算逃出去了。為什麼可以這樣判斷? 因為,當人的座標在邊界時(因為1和R和C都是屬於圖的邊界座標)他下一步肯定會逃出去,不可能出現被燒死的情況。所以,這道題中判斷逃出邊界方式實際上就為:當人到達邊界時就算成功!但是這樣判斷的話,由於人在逃出去的時候,座標肯定在邊界外面,所以當我們判斷成功後,要將步數+1。
第二種是當人的x座標等於0或者R+1時,y座標等於0或者C+1時,就算逃出去了。這樣做跟第一種的區別就是當人的座標在邊界外時,才判斷他逃了出去。(第一種方式則是:當人的座標在邊界時,就判斷他逃了出去)似乎第二種更容易進行理解。但是請注意:使用第二種方式時,由於這道題是多組輸入輸出,每組測試例項中的地圖都不一樣。因此,我們需要在每組測試之前都要去清一下地圖才可。(想一下為什麼要這樣)
其它的內容都跟正常的bfs一樣,並且推薦在使用bfs進行搜尋時,採用結構體來儲存搜尋到的座標。而儘量不要使用pair,用pair不好處理!
程式碼如下:(第一種方式)
#include <iostream>
#include <cstdio>
#include <deque>
using namespace std;
char Graph[1500][1500]; //定義圖
int R, C; //定義R行,C列
int Count; //代表測試次數
bool vis[1500][1500]; //代表訪問陣列(人)
int x[4] = { 1,0,-1,0 }; //定義方向陣列x
int y[4] = { 0,1,0,-1 }; //定義方向陣列y
struct Point //定義代表頂點的結構體
{
int x;
int y;
int step;
};
deque<struct Point> fire; //代表火進行bfs的佇列
deque<struct Point> Person; //代表人進行bfs的佇列
int bfs(struct Point start); //代表進行bfs的函式
void init(); //代表進行初始化的函式
void firemove(); //代表火進行蔓延的函式
void init()
{
int i, j;
for (i = 0; i < 1500; i++)
{
for (j = 0; j < 1500; j++)
{
vis[i][j] = false;
}
}
fire.clear(); //清佇列(人)
Person.clear(); //清佇列(火)
}
void firemove()
{
int i, j;
struct Point firetemp; //代表當前遍歷的火的下標
int movex, movey; //代表火移動之後的下標
int firecount = fire.size(); //代表火的個數
for (i = 0; i < firecount; i++)
{
firetemp = fire.front();
fire.pop_front();
for (j = 0; j < 4; j++)
{
movex = firetemp.x + x[j]; //代表火嘗試向四個方向進行蔓延
movey = firetemp.y + y[j];
if (movex >= 1 && movex <= R && movey >= 1 && movey <= C && Graph[movex][movey] == '.') //火只能在迷宮中進行燃燒,因此火的座標不可出邊界
{
fire.push_back({ movex,movey,0 }); //將火進行入隊
Graph[movex][movey] = 'F'; //將燃燒的地方進行修改為F,代表燃燒過的地方就不能走了
}
}
}
}
int bfs(struct Point start)
{
int movex, movey; //代表人移動之後的座標
struct Point temp;
int personcount; //定義人的座標個數
int i;
Person.push_back({ start.x,start.y,start.step }); //代表從起點開始入隊
vis[start.x][start.y] = true; //代表起點已經訪問過。
while (!Person.empty())
{
personcount = Person.size();
firemove(); //代表讓火先走一步
while (personcount--) //代表人也可以一次性往四個方向去行走
{
temp = Person.front();
Person.pop_front();
if (temp.x == 1 || temp.x == R || temp.y == 1 || temp.y == C) //如果人的座標出了邊界
{
return temp.step + 1; //返回出界座標所對應的步數
}
for (i = 0; i < 4; i++)
{
movex = temp.x + x[i]; //代表人嘗試向四個方向行走
movey = temp.y + y[i];
if (movex >= 1 && movex <= R && movey >= 1 && movey <= C && vis[movex][movey] == false && Graph[movex][movey] == '.') //依據本題題意,人的座標是可以出邊界的,如果出了邊界代表逃脫成功
{
vis[movex][movey] = true; //代表目前點已經走過
Person.push_back({ movex,movey,temp.step + 1 }); //將移動後的座標進行入隊,並且步數加1
}
}
}
}
return -100; //如果迴圈結束,函式仍沒有結束的話,代表人不可能逃脫
}
int main()
{
int i, j, k;
int result; //代表儲存結果的變數
while (scanf("%d", &Count) != EOF)
{
for (k = 0; k < Count; k++)
{
init(); //執行初始化函式
scanf("%d %d", &R, &C);
for (i = 1; i <= R; i++)
{
for (j = 1; j <= C; j++)
{
scanf(" %c", &Graph[i][j]);
if (Graph[i][j] == 'F') //如果這個座標為火(在這道題中不止一個火)
{
fire.push_back({ i,j,0 }); //將火入佇列
}
}
}
for (i = 1; i <= R; i++)
{
for (j = 1; j <= C; j++)
{
if (Graph[i][j] == 'J')
{
result = bfs({ i,j,0 }); //代表以喬為起點進行bfs
if (result == -100)
{
cout << "IMPOSSIBLE" << endl;
}
else
{
cout << result << endl;
}
}
}
}
}
}
}
程式碼如下:(第二種方式)
#include <iostream>
#include <cstdio>
#include <deque>
using namespace std;
char Graph[1500][1500]; //定義圖
int R, C; //定義R行,C列
int Count; //代表測試次數
bool vis[1500][1500]; //代表訪問陣列(人)
int x[4] = { 1,0,-1,0 }; //定義方向陣列x
int y[4] = { 0,1,0,-1 }; //定義方向陣列y
struct Point //定義代表頂點的結構體
{
int x;
int y;
int step;
};
deque<struct Point> fire; //代表火進行bfs的佇列
deque<struct Point> Person; //代表人進行bfs的佇列
int bfs(struct Point start); //代表進行bfs的函式
void init(); //代表進行初始化的函式
void firemove(); //代表火進行蔓延的函式
void init()
{
int i, j;
for (i = 0; i < 1500; i++)
{
for (j = 0; j < 1500; j++)
{
Graph[i][j] == '.';
vis[i][j] = false;
}
}
fire.clear(); //清佇列(人)
Person.clear(); //清佇列(火)
}
void firemove()
{
int i,j;
struct Point firetemp; //代表當前遍歷的火的下標
int movex, movey; //代表火移動之後的下標
int firecount = fire.size(); //代表火的個數
for (i = 0; i < firecount; i++)
{
firetemp = fire.front();
fire.pop_front();
for (j = 0; j < 4; j++)
{
movex = firetemp.x + x[j]; //代表火嘗試向四個方向進行蔓延
movey = firetemp.y + y[j];
if (movex >= 1 && movex <= R && movey >= 1 && movey <= C && Graph[movex][movey] == '.') //火只能在迷宮中進行燃燒,因此火的座標不可出邊界
{
fire.push_back({ movex,movey,0 }); //將火進行入隊
Graph[movex][movey] = 'F'; //將燃燒的地方進行修改為F,代表燃燒過的地方就不能走了
}
}
}
}
int bfs(struct Point start)
{
int movex, movey; //代表人移動之後的座標
struct Point temp;
int personcount; //定義人的座標個數
int i;
Person.push_back({ start.x,start.y,start.step }); //代表從起點開始入隊
vis[start.x][start.y] = true; //代表起點已經訪問過。
while (!Person.empty())
{
personcount = Person.size();
firemove(); //代表讓火先走一步
while (personcount--) //代表人也可以一次性往四個方向去行走
{
temp = Person.front();
Person.pop_front();
if (temp.x == 0 || temp.x == R+1 || temp.y == 0 || temp.y == C+1) //如果人的座標出了邊界
{
return temp.step; //返回出界座標所對應的步數
}
for (i = 0; i < 4; i++)
{
movex = temp.x + x[i]; //代表人嘗試向四個方向行走
movey = temp.y + y[i];
if (vis[movex][movey] == false && Graph[movex][movey] == '.' || (movex == 0 || movex == R+1 || movey == 0 || movey == C+1) ) //依據本題題意,人的座標是可以出邊界的,如果出了邊界代表逃脫成功
{
vis[movex][movey] = true; //代表目前點已經走過
Person.push_back({ movex,movey,temp.step+1}); //將移動後的座標進行入隊,並且步數加1
}
}
}
}
return -100; //如果迴圈結束,函式仍沒有結束的話,代表人不可能逃脫
}
int main()
{
int i,j,k;
int result; //代表儲存結果的變數
while (scanf("%d",&Count) != EOF)
{
for (k = 0; k < Count; k++)
{
init(); //執行初始化函式
scanf("%d %d", &R, &C);
for (i = 1; i <= R; i++)
{
for (j = 1; j <= C; j++)
{
scanf(" %c", &Graph[i][j]);
if (Graph[i][j] == 'F') //如果這個座標為火(在這道題中不止一個火)
{
fire.push_back({ i,j,0 }); //將火入佇列
}
}
}
for (i = 1; i <= R; i++)
{
for (j = 1; j <= C; j++)
{
if (Graph[i][j] == 'J')
{
result = bfs({ i,j,0 }); //代表以喬為起點進行bfs
if (result == -100)
{
cout << "IMPOSSIBLE" << endl;
}
else
{
cout << result << endl;
}
}
}
}
}
}
}