1. 程式人生 > 實用技巧 >FZU 2150 Fire Game

FZU 2150 Fire Game

題目如下:

放火燒山

法外狂徒張三在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;
	}
}