FZU 2150 Fire Game
阿新 • • 發佈:2020-12-10
題目如下:
放火燒山
法外狂徒張三在n*m的平地上放火玩,#表示草,張三有分身,他的分身和他本人分別選一個#格子點火,火可以向上向下向左向右在有草的格子蔓延,點火的地方時間為0,蔓延至下一格的時間依次加一。求燒完所有的草需要的最少時間。如不能燒完輸出-1。
Input
第一行,輸入一個T,表示有T組測試資料。
每組資料由一個n,m分別表示行列
1 <= T <=100, 1 <= n <=10, 1 <= m <=10
Output
輸出最少需要的時間>
Sample Input
4
3 3
.#.
.#.
3 3
.#.
#.#
.#.
3 3
...
#.#
...
3 3
..#
#.#
Sample Output
Case 1: 1
Case 2: -1
Case 3: 0
Case 4: 2
解題思路:
這道題首先說的是最短時間,因此這道題我們要用bfs來進行求解。其次在這道題中我們可以將連起來的草當做一個連通塊。然後去求連通塊的個數,當連通塊的個數大於2時,就代表至少有一個連通塊無法被燒。(因此導致不能燒完的結果輸出-1) 在這道題中有兩個起點,起點可以是任意的草('#')。由於起點不同,因此所花費的時間也會不相同。所以,我們需要遍歷所有的起點,從這些起點中尋找燃燒時間最少的進行輸出。並且起點可能會有相同的情況。
關於這道題的剪枝處理
如果要輸出最短時間的話,則連通塊的個數至多為2。由於一個起點在一個連通塊當中必然會將連通塊燒完。因此當有兩個連通塊時,我們需要將兩個起點只在一個連通塊的情況進行剪枝。(因為當有兩個連通塊時,如果兩個起點都在一個連通塊中,那麼結果肯定是-1。由於有兩個起點,兩個連通塊的情況是一定有結果的。因此,我們不需要這樣的無用搜索。只需要搜尋兩個起點在不同的聯通塊的情況就可以。)當只有一個連通塊時,我們就不需要進行剪枝了。(因為兩個起點都只能在一個連通塊中,然而這時是一定有結果的)
雖然這道題有兩個起點,但是我們可以將這兩個起點放在同一個bfs當中進行遍歷。(跟之前的bfs的遍歷方式沒有什麼區別) 數連通塊的函式,我們可以採用bfs/dfs均可。(數連通塊類似於POJ的 Lake Counting 這道題,不懂的話可以去看一下筆記)
Lake Counting 筆記如下: https://www.cnblogs.com/gao79135/p/13979047.html
程式碼如下:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <deque>
#include <algorithm>
using namespace std;
int n, m, t;
struct Point
{
int x; //代表每個點的x座標
int y; //代表每個點的y座標
int step; //代表到達該點的步數
Point() {} //空建構函式
Point(int x, int y,int step) : x(x), y(y),step(step) {} //在這個測試平臺中,我們需要加一個建構函式,不然的話就會CE
};
deque<Point> Q; //定義進行bfs的佇列
const int INF = 0x3f3f3f3f; //定義極大值
char Graph[15][15]; //定義圖
int bfs(Point start); //代表進行bfs的函式
void init(); //代表進行初始化的函式
void find(Point start); //代表找尋連通塊的函式
bool vis[15][15]; //代表點的訪問狀態
int connect[15][15]; //代表點屬於哪個連通塊
int Count = 0; //代表聯通塊數
int x[4] = { 1,0,-1,0 };//代表移動的方向(x座標)
int y[4] = { 0,1,0,-1 };//代表移動的方向(y座標)
int sx, sy; //代表第一個起點
int Time = INF; //定義最小的時間數
void init()
{
int i, j;
for (i = 0; i < 15; i++)
{
for (j = 0; j < 15; j++)
{
connect[i][j] = 0; //初始化為0(代表圖中的草不屬於任何連通塊)
}
}
}
void find(Point start) //在搜尋連通塊的函式中,每個頂點的step沒有任何作用!step是在bfs函式中進行記錄最短時間用的
{
int movex, movey; //代表移動之後的座標
int i;
Point temp; //代表當前遍歷的頂點
Count++; //代表生成的是第Count連通塊
connect[start.x][start.y] = Count; //代表起點屬於第Count個連通塊
Q.push_back(start); //將起點進行入隊
while (!Q.empty())
{
temp = Q.front();
Q.pop_front();
for (i = 0; i < 4; i++)
{
movex = temp.x + x[i];
movey = temp.y + y[i];
if (movex >= 0 && movex < n && movey >= 0 && movey < m && Graph[movex][movey] == '#' && connect[movex][movey] == 0) //代表從當前點找尋下一個不屬於任何連通塊的點
{
Q.push_back(Point(movex,movey,0));//將搜尋到的點入佇列(由於之前定了一個有參建構函式,因此我們需要將引數傳遞進建構函式來生成一個結構體,然後再入佇列)
connect[movex][movey] = Count; //將搜尋到的點歸屬於第Count個連通塊中
}
}
}
}
int bfs(Point start)
{
int i;
int movex, movey; //代表移動之後的座標
int Max = -INF; //代表整道題所需要的最短時間(因為是以兩個起點進行bfs,那麼整道題中的最短時間應該取以兩個起點進行燃燒的最大時間)
Point temp; //代表當前正在遍歷的頂點
memset(vis, false, sizeof(vis)); //代表初始化vis陣列為false
Q.push_back(Point(sx,sy,0)); //將第一個起點進行入隊
Q.push_back(Point(start.x,start.y,0)); //將第二個起點進行入隊
vis[sx][sy] = true; //第一個/第二個起點均被訪問過
vis[start.x][start.y] = true;
while (!Q.empty())
{
temp = Q.front();
Q.pop_front();
Max = max(Max, temp.step); //取每個遍歷的點的最大時間(取以兩個起點進行遍歷的最大時間(假設有兩個聯通塊,每個連通塊各有一個起點,假設第一個起點燃燒了1秒就結束了,但是第二個起點燃燒了3秒才結束,所以整道題的最小時間應該取3秒)
for (i = 0; i < 4; i++)
{
movex = temp.x + x[i];
movey = temp.y + y[i];
if (movex >= 0 && movex < n && movey >= 0 && movey < m && Graph[movex][movey] == '#' && vis[movex][movey] == false)
{
vis[movex][movey] = true;
Q.push_back(Point(movex,movey,temp.step + 1));
}
}
}
return Max;
}
int main()
{
int i, j; //迭代變數i,j
int k;
int a, b; //迭代變數a,b
int sum = 1; //代表示例個數
scanf("%d", &t);
for (k = 0; k < t; k++)
{
init();
scanf("%d %d", &n, &m);
for (i = 0; i < n; i++)
{
for (j = 0; j < m; j++)
{
scanf(" %c", &Graph[i][j]);
}
}
for (i = 0; i < n; i++)
{
for (j = 0; j < m; j++)
{
if (connect[i][j] == 0 && Graph[i][j] == '#') //代表將不屬於任何連通塊的草為起點進行遍歷
{
find(Point(i, j, 0));
}
}
}
if (Count > 2) //若連通塊的數量超過兩個,則此題無解。(因為撐死就有兩個起點進行bfs遍歷,如果出現了2個以上的連通塊,則肯定有連通塊無法被燒)
{
cout << "Case " << sum++ << ": " << -1 << endl;
}
else
{
for (i = 0; i < n; i++) //通過迴圈在連通塊中遍歷所有的起點情況,並從這些起點遍歷所花費的時間中,篩選出最小時間。
{
for (j = 0; j < m; j++)
{
if (connect[i][j] == 1) //代表在第一個連通塊中尋找起點(並且在連通塊中可能會以不同的起點進行遍歷,從這些起點中找到最少的燃燒時間)
{
sx = i;
sy = j;
for (a = 0; a < n; a++)
{
for (b = 0; b < m; b++)
{
if (connect[a][b] == Count) //代表在第Count個連通塊中尋找起點。(因為Count的個數最多等於2,當圖中有兩個聯通塊時,由於上面已經選擇了第一個連通塊,所以此時Count就一定為2。這樣就構成了剪枝效果。如果圖中只有一個聯通塊的話,Count就為1(代表兩個起點同時在一個連通塊中進行遍歷)
{
Time = min(Time, bfs(Point(a, b, 0))); //代表進行bfs搜尋並取以不同起點的最短時間(因為在這道題中可以任取'#'進行搜尋,因此會有不同的起點,由於選擇不同的起點,其結果也會不一樣,所以我們應該選擇花費時間最小的起點)
}
}
}
}
}
}
cout << "Case " << sum++ << ": " << Time << endl;
}
Time = INF;
Count = 0;
}
}