1. 程式人生 > 實用技巧 >POJ 3669 Meteor Shower

POJ 3669 Meteor Shower

原題目如下:

Bessie聽說有場史無前例的流星雨即將來臨;有讖言:隕星將落,徒留灰燼。為保生機,她誓將找尋安全之所(永避星墜之地)。目前她正在平面座標系的原點放牧,打算在群星斷其生路前轉移至安全地點。

此次共有M (1 ≤ M ≤ 50,000)顆流星來襲,流星i將在時間點Ti (0 ≤ Ti ≤ 1,000) 襲擊點 (Xi, Yi) (0 ≤ Xi ≤ 300; 0 ≤ Yi ≤ 300)。每顆流星都將摧毀落點及其相鄰四點的區域。

Bessie在0時刻時處於原點,且只能行於第一象限,以平行與座標軸每秒一個單位長度的速度奔走於未被毀壞的相鄰(通常為4)點上。在某點被摧毀的剎那及其往後的時刻,她都無法進入該點。

尋找Bessie到達安全地點所需的最短時間。


Input - 輸入

* 第1行: 一個整數: M

* 第2..M+1行: 第i+1行包含由空格分隔的三個整數: Xi, Yi, and Ti


Output - 輸出

* 僅一行: Bessie尋得安全點所花費的最短時間,無解則為-1。


Sample Input - 輸入樣例

4

0 0 2

2 1 2

1 1 2

0 3 5


Sample Output - 輸出樣例

5


解題思路:

這道題是bfs的經典好題,由於這道題沒有給我們地圖,所以我們要自己設計出地圖。由於流星的轟炸範圍為0-300,所以我們的地圖大小不能少於300。建議設定比300大一點點即可。我們可以選擇直接用一個二維陣列儲存地圖即可,每一個位置上儲存流星破壞這個位置的最小時間。(因為流星破壞位置時,有些時候破壞的位置會重疊!)之後,再進行比較判斷,根據當前的時間和這個位置流星破壞的時間來決定可不可以進行行走。因此,我們需要在剛開始的時候就要把所有的流星都要落下來,去構造地圖。第二點,這道題一定不可以走回頭路,如果走回頭路的話會因為複雜的情況導致結果為TLE。所以,我們必須要把回頭路的情況剪掉。第三點,這道題可以不進行移動,只要流星不砸到(0,0)點,我們就可以一直再原點呆著,最後輸出最小時間為0。第四點,這道題必須再第一象限內進行移動,並且只能上下左右四個方向進行移動。所以移動的座標不能為負數,並且請注意,你是可以走出轟炸範圍0-300的,也就是說當你走到300之後的地方時也算你成功。通俗來說,只要座標都大於等於0就行。只要跳過了這些坑,這道題就跟普通的bfs沒有什麼區別了。

並且,這道題好像並不是多組輸入輸出!


程式碼如下:(參照大佬的AC程式碼寫的,自己寫TLE)

#include <iostream>
#include <cstdio>
#include <queue>
#include <cstring>       //memset函式的標頭檔案
#include <algorithm>
using namespace std;
int m;
struct point
{
	int t;
	int x, y;
} temp, start;                  //temp代表當前遍歷的頂點,start代表移動的人
int Graph[305][305];            //定義一個圖
queue<point> Q;                 //定義一個佇列用來儲存當前定義的點
void bfs();         //代表廣度優先搜尋的函式
void bfs()
{
	int i, j;
	if (Graph[0][0] == -1)   //代表所有流星都砸完之後,若人還在原點沒被砸到,直接就返回0(代表沒有移動,就可以躲過流星)
	{
		printf("0");
		return;
	}
	if (Graph[0][0] == 0)   //如果,人還沒有移動的時候,就被流星砸到了(時間為0時,沒來得及移動,就被砸),直接就返回-1(代表移動不了,沒有路線可行走)
	{
		printf("-1");
		return;
	}
	start.x = start.y = start.t = 0;
	Q.push(start);         //將移動的人進行入隊
	while (!Q.empty())
	{
		temp = Q.front();
		Q.pop();
		for (i = -1; i <= 1; i++)
		{
			for (j = -1; j <= 1; j++)
			{
				if (i == 0 || j == 0)   //代表人往五個方向移動(包括(0,0)本身,因為人也可以不移動)
				{
					start.x = temp.x + i;
					start.y = temp.y + j;
					start.t = temp.t + 1;              //代表將時間+1
					if (start.x < 0 || start.y < 0)    //如果人移動超過了第一象限
					{
						continue;
					}
					else if (Graph[start.x][start.y] == -1)
					{
						printf("%d", start.t);                      //如果流星砸完之後,有空位可以進行移動,則直接返回最短時間
						return;
					}
					else if (Graph[start.x][start.y] <= start.t)   //如果當前移動座標的時間段start.t,比流星砸下來的時間段(Graph[start.x][start.y]要大的話,證明在你移動的時候,這個點已經被砸完了,不能進行移動了。(同時,也是避免走回頭路的判斷方式)
					{
						continue;
					}
					Graph[start.x][start.y] = start.t;          //當前移動座標的時間段,比流星砸下來的時間段小的話,代表當你進行移動時,流星還沒有砸下來,證明可以進行移動。(例如:start.t為1但是Graph[start.x][start.y] = 3,就可以進行移動,因為當你在1時間段走時,流星還沒有落下來。),並且,走完之後,要將Graph[start.x][start.y]進行更新為當前時間段,這個就是避免走回頭路,不然的話,按照題中要求是可以進行走回頭路的,但是,如果走回頭路的話,這道題就會TLE,並且,走回頭路也會讓情況變得越來越糟。
					Q.push(start);                              //將移動之後的點進行入隊
				}
			}
		}
	}
	printf("-1");     //當隊列出隊完畢後,如果還是沒有可移動的空地,則就代表沒有這樣的路徑可以走,為-1

}

int main()
{
	int i,j,k;
	scanf("%d", &m);
	memset(Graph, -1, sizeof(Graph)); 
	for (i = 1; i <= m; i++)
	{    //讓流星提前下落,之後再進行bfs
		scanf("%d %d %d", &temp.x, &temp.y, &temp.t);        
		for (j = -1; j <= 1; j++)
		{
			for (k = -1; k <= 1; k++)
			{
				if (j == 0 || k == 0)
				{                                     //流星砸中點之外,還砸了相鄰四個點(上下左右)
					if (temp.x+j >=0 && temp.y+k >=0) //且砸的點全都在第一象限(x,y不能為負數)
					{  
						if (Graph[temp.x + j][temp.y + k] == -1)
						{
							Graph[temp.x + j][temp.y + k] = temp.t;
						}
						else
						{
							Graph[temp.x + j][temp.y + k] = min(temp.t, Graph[temp.x + j][temp.y + k]);
						}
					}
				}
			}
		}
	}
	bfs();       //開始進行bfs()
	return 0;
}