1. 程式人生 > >UVa Problem 10187 From Dusk till Dawn (從黃昏到拂曉)

UVa Problem 10187 From Dusk till Dawn (從黃昏到拂曉)

// From Dusk till Dawn (從黃昏到拂曉)
// PC/UVa IDs: 110907/10187, Popularity: B, Success rate: average Level: 3
// Verdict: Accepted
// Submission Date: 2011-10-02
// UVa Run Time: 0.048s
//
// 版權所有(C)2011,邱秋。metaphysis # yeah dot net
//
// [問題描述]
// Vladimir has white skin, very long teeth and is 600 years old, but this is no
// problem because Vladimir is a vampire.
// Vladimir has never had any problems with being a vampire. In fact, he is a very
// successful doctor who always takes the night shift and so has made many friends
// among his colleagues. He has a very impressive trick which he shows at dinner
// partys: He can tell tell blood group by taste.
// Vladimir loves to travel, but being a vampire he has to overcome three problems.
//
// First, he can only travel by train because he has to take his coffin with him.
// (On the up side he can always travel first class because he has invested a lot
// of money in long term stocks.)
// Second, he can only travel from dusk till dawn, namely from 6 pm to 6 am. During
// the day he has to stay inside a train station.
// Third, he has to take something to eat with him. He needs one litre of blood
// per day, which he drinks at noon (12:00) inside his coffin. 
//
// You should help Vladimir to find the shortest route between two given cities,
// so that he can travel with the minimum amount of blood. (If he takes too much
// with him, people will ask funny questions like "What do you do with all that
// blood?")
//
// [輸入]
// The first line of the input will contain a single number telling you the number
// of test cases.
// Each test case specification begins with a single number telling you how many
// route specifications follow.
// Each route specification consists of the names of two cities, the departure
// time from city one and the total travelling time. The times are in hours. Note
// that Vladimir can't use routes departing earlier than 18:00 or arriving later
// than 6:00.
// There will be at most 100 cities and less than 1000 connections. No route takes
// less than one hour and more than 24 hours. (Note that Vladimir can use only
// routes with a maximum of 12 hours travel time (from dusk till dawn).) All city
// names are shorter than 32 characters.
// The last line contains two city names. The first is Vladimir's start city, the
// second is Vladimir's destination. 
//
// [輸出]
// For each test case you should output the number of the test case followed by
// "Vladimir needs # litre(s) of blood." or "There is no route Vladimir can take." 
//
// [樣例輸入]
// 2
// 3
// Ulm Muenchen 17 2
// Ulm Muenchen 19 12
// Ulm Muenchen 5 2
// Ulm Muenchen
// 10
// Lugoj Sibiu 12 6
// Lugoj Sibiu 18 6
// Lugoj Sibiu 24 5
// Lugoj Medias 22 8
// Lugoj Medias 18 8
// Lugoj Reghin 17 4
// Sibiu Reghin 19 9
// Sibiu Medias 20 3
// Reghin Medias 20 4
// Reghin Bacau 24 6
// Lugoj Bacau
//
// [樣例輸出]
// Test Case 1.
// There is no route Vladimir can take.
// Test Case 2.
// Vladimir needs 2 litre(s) of blood.
//
// [解題方法]
// 由題意可知,Vladimir 在乘坐火車從一個城市到另一個城市時可以有多種路線,其中出發時間不在 18:00
// 之後且到達時間不在 06:00 之前的都可以不予考慮,然後對這些滿足要求的路線用 DAG 建模,問題即求
// 圖中兩點的最短路線。其中需要注意的是,由於需要將攜帶血量最小化,故需要考慮從起始城市到終點城市的
// 所有路線,因為有可能從 A 到 B 的車是 18:00 出發,20:00 到達 B,然後再從 B 到 C,有出發時間
// 為 21:00,24:00 到達 C 的火車路線,這樣若從 A 到 C 就不需在車站停留,從而不需喝 1 升的血,
// 所以雖然總路線不是最短的,但是攜帶血量可以是最少的,故需要遍歷可能的通路來獲取最少使用血量,這個
// 可以通過使用寬度優先遍歷的思想來解決。

#include <iostream>
#include <queue>
#include <map>

using namespace std;

#define MAXN 100
#define EARLIER 18
#define LATER 6
#define HOURS 24
#define NO_ROUTE (-1)

// 當前路線的狀態,其中 city 標誌當前到達的城市,time 表示到達的時間,litres 表示已經使用的血量。
class state
{
public:
	int city, time, litres;

	// 使用血量少的路線先處理。
	bool operator<(const state ¤t) const
	{
		return litres > current.litres;
	}
};

// 火車路線。
class route
{
public:
	int departure;		// 出發時間。
	int arrive;		// 到達時間。
	int to;			// 到達城市。
};

// 城市之間的火車路線。
vector < route > edges[MAXN + 1];

// 使用優先佇列的方法來遍歷所有從起始城市 from 到 終點城市 to 的路線。在此過程中,先處理使用血
// 量少的路線,當發現當前路線的狀態 state 所標誌的城市 city 已經為目標城市 to 時,表明當前需要
// 血量最少的路線即為該 state 所走的路線。
int travel(int from, int to)
{
	// 建立優先佇列,需要血量少的路線先處理。
	priority_queue < state > states;

	// 將當前城市為 from,到達時間為 18:00 的狀態新增到優先佇列中,表示已經從一個虛擬的地
	// 方到達了起始城市 from,到達時間小於起始城市 from 的所有可用火車路線的出發時間,當前
	// 已經使用血量為 0 升。
	states.push((state){from, EARLIER, 0});

	// 處理佇列中的路線狀態。
	while (!states.empty())
	{
		// 取出隊首的路線狀態處理。
		state current = states.top();
		states.pop();

		// 若當前城市已經為目標城市,則直接返回當前路線所使用的血量。
		if (current.city == to)
			return current.litres;

		// 遍歷當前到達城市至其他城市的火車路線,根據情況決定是否需要取用 1 升的血。
		for (int r = 0; r < edges[current.city].size(); r++)
		{
			int used = current.litres;
			// 若到達時間晚於該火車路線出發時間,則需在此車站停留一箇中午,取血 1 升。
			if (current.time > edges[current.city][r].departure)
				used++;

			states.push((state){edges[current.city][r].to,
					edges[current.city][r].arrive, used});
		}

	}

	// 起始城市與目標城市無連通路線。
	return NO_ROUTE;
}

int main(int ac, char *av[])
{
	int test, routes, cases = 1;
	int from, to;
	string start, destination;
	int departure, traveling;

	cin >> test;
	while (test--)
	{
		map < string, int > cities;

		for (int i = 0; i < MAXN + 1; i++)
			edges[i].clear();

		cin >> routes;
		for (int i = 1; i <= routes; i++)
		{
			// 讀入起始和終點城市、出發時間、旅行時間,並將其插入到 map 中。
			cin >> start >> destination >> departure >> traveling;
			if (cities.find(start) == cities.end())
			{
				from = cities.size();
				cities[start] = from;
			}
			else
				from = cities[start];

			if (cities.find(destination) == cities.end())
			{
				to = cities.size();
				cities[destination] = to;
			}
			else
				to = cities[destination];

			// 滿足條件的路線則新增到有向圖中。若出發時間為凌晨,則加上 24 小時
			// 以統一時間起點。
			departure += (departure <= LATER ? HOURS : 0);
			if (departure >= EARLIER
				&& (departure + traveling) <= (HOURS + LATER))
				edges[from].push_back((route){departure,
						(departure + traveling), to});
		}

		// 出發和到達城市。
		cin >> start >> destination;

		cout << "Test Case " << cases++ << "." << endl;

		// 處理特殊情況:起始和目標城市相同。
		if (start == destination)
		{
			cout << "Vladimir needs 0 litre(s) of blood." << endl;
			continue;
		}

		// 處理特殊情況:起始或目標城市不在輸入資料中。
		if (cities.find(start) == cities.end()
			|| cities.find(destination) == cities.end())
		{
			cout << "There is no route Vladimir can take." << endl;
			continue;
		}

		// 取出起始和目標城市的序號。
		from = cities[start];
		to = cities[destination];

		// 遍歷所有路線以找到從城市 from 到城市 to 所需的最少血量。
		int litres = travel(from, to);

		if (litres == NO_ROUTE)
			cout << "There is no route Vladimir can take." << endl;
		else
			cout << "Vladimir needs " << litres << " litre(s) of blood." << endl;
	}

	return 0;
}