uva 10557 XYZZY

It has recently been discovered how to run open-source software on the Y-Crate gaming device. A number of enterprising designers have developed Advent-style games for deployment on the Y-Crate. Your job is to test a number of these
designs to see which are winnable. Each game consists of a set of up to 100 rooms. One of the rooms is the start and
one of the rooms is the finish. Each room has an energy value between -100 and +100.
One-way doorways interconnect pairs of rooms. The player begins in the start room with 100 energy points. She may pass through any doorway that connects the room she is in to another room, thus entering the other room. The energy value of
this room is added to the player’s energy. This process continues until she wins by entering the finish room or dies by running out of energy (or quits in frustration). During her adventure the player may enter the same room several times, receiving its energy each time.
Input
The input consists of several test cases. Each test case begins with n, the number of rooms. The rooms are numbered from 1 (the start room) to n (the finish room). Input for the n rooms follows. The input for each room consists of one or more lines containing:
• the energy value for room i
• the number of doorways leaving room i
• a list of the rooms that are reachable by the doorways leaving room i
The start and finish rooms will always have energy level 0. A line containing ‘-1’ follows the last
test case.
Output
In one line for each case, output ‘winnable’ if it is possible for the player to win, otherwise output
‘hopeless’.
Sample Input
5
0 1 2
-60 1 3
-60 1 4
20 1 5
0 0
5
0 1 2
20 1 3
-60 1 4
-60 1 5
0 0
5
0 1 2
21 1 3
-60 1 4
-60 1 5
0 0
5
0 1 2
20 2 1 3
-60 1 4
-60 1 5
0 0
-1
Sample Output
hopeless
hopeless
winnable
winnable

中文:

有n個房間,每個房間裏面有分數,分數值從-100到100,每個可能會有一定數量的門連通其他房間,你每經過一個房間時就要把房間中的分數加到自己身上。你初始的時候手裏有100分,現在問你能否從第一號房間走到第n號房間,在尋找路徑的過程中,分數不允許小於等於0。

數據提示:

圖可能不連通,有重邊,有自環!

代碼:

#include <bits/stdc++.h>


using namespace std;
const int inf=-9999999;

const int maxn = 300;
int N;
struct Edge
{
	int from, to, dist;
};
struct BellmanFord
{
	int n, m;//頂點與邊
	vector<Edge> edges;
	vector<int> G[maxn];
	bool inq[maxn];
	int d[maxn];
	int cnt[maxn];
	void init(int n)
	{
		this->n = n;
		for (int i = 0; i<n; i++)
			G[i].clear();
		edges.clear();
	}
	void AddEdge(int from, int to, int dist)
	{
		edges.push_back(Edge{ from, to, dist });
		m = edges.size();
		G[from].push_back(m - 1);
	}
	bool solve()
	{
		//for (int i = 0; i<edges.size(); i++)
		// cout << edges[i].from << " " << edges[i].to << " " << edges[i].dist << endl;
		queue<int> Q;
		memset(inq, 0, sizeof(inq));
		memset(cnt, 0, sizeof(cnt));
		for (int i = 0; i<n; i++)
		{
			d[i] = inf;
			inq[0] = true;
			Q.push(i);
		}
		d[0] = 100;
		while (!Q.empty())
		{
			int u = Q.front();
			Q.pop();
			inq[u] = false;
			for (int i = 0; i<G[u].size(); i++)
			{
				Edge& e = edges[G[u][i]];
				if (d[e.to]<d[u] + e.dist && d[u] + e.dist>0)//找權值最大路徑,而且更新的路徑必須大於0
				{
					d[e.to] = d[u] + e.dist;
					if (e.to == n - 1)
                        return true;//有一條路徑直接到終點
					if (!inq[e.to])
					{
						Q.push(e.to);
						inq[e.to] = true;
						if (++cnt[e.to]>n)
                            return true;//存在正環
					}
				}
			}
		}
		if (d[n - 1]>0)//最終結果大於0
			return true;
		else
			return false;
	}
};

bool mat[maxn][maxn];
bool mark[maxn];
BellmanFord BF;
vector<int> tmp[maxn];
int energy[maxn];


void floyed(int n)
{
	for (int k = 0; k<n; k++)
	{
		for (int i = 0; i<n; i++)
		{
			for (int j = 0; j<n; j++)
			{
				if (mat[i][k] && mat[k][j])
					mat[i][j] = 1;
			}
		}
	}
}
//fstream in,out;
int main()
{
	ios::sync_with_stdio(false);
	while (cin >> N)
	{
	    if(N==-1)
            return 0;
		memset(energy, 0, sizeof(energy));
		memset(mat, 0, sizeof(mat));
		memset(mark, 0, sizeof(mark));
		int vc, e, v;
		set<int> si;
		for (int i = 0; i<N; i++)
		{
			tmp[i].clear();
			cin >> energy[i] >> vc;
			si.clear();
			for (int j = 0; j<vc; j++)
			{
				cin >> v;
				si.insert(v - 1);
			}
			tmp[i].assign(si.begin(), si.end());
		}

		for (int i = 0; i<N; i++)
		{
			mat[i][i] = 1;
			for (int j = 0; j<tmp[i].size(); j++)
				mat[i][tmp[i][j]] = 1;
		}
		floyed(N);//找出圖的連通關係

		for (int i = 0; i<N; i++)
		{
		    //判斷第1個節點經過第i個節點能否到第n個節點
			if (mat[0][i] && mat[i][N - 1])
                mark[i] = 1;
		}

		BF.init(N * 2);
		for (int i = 0; i<N; i++)
		{
			if (mark[i])//在1到n節點路徑上的點才添加邊
			{
				BF.AddEdge(i * 2, i * 2 + 1, energy[i]);//把每個節點擴展成邊,邊上的權值就是房間的分數
				for (int j = 0; j<tmp[i].size(); j++)
				{
					if (mark[tmp[i][j]])
						BF.AddEdge(i * 2 + 1, tmp[i][j] * 2, 0);
				}
			}
		}

		if (BF.solve())
			cout << "winnable" << endl;
		else
			cout << "hopeless" << endl;

	}
	return 0;
}

解答:

小白書上的基礎數據結構章節的練習題,本來以爲題目可以用暴力搜索弄掉,結果一直超時-_-

設計出算法以後一直卡數據,發現數據中有重邊。

仔細想想,給你一個帶權的有向圖,讓你判斷這個圖是否存在一條路徑能從起點走到終點,而且要求在遍歷這條路徑時分數始終不能小於等於0,會有什麼情況?

首先考慮點不連通的情況,如下圖:
在這裏插入圖片描述
從頂點1到頂點4,無論怎麼走都走不到!

其次,與路徑中無關緊要的節點,要去除,如下圖:
在這裏插入圖片描述

4節點和5節點是對路徑上的權值沒有貢獻的

尋找路徑的問題,除了搜索,首先就會想到最短路徑,那麼,如果找到了一條最短路徑,沿着這條最短路徑,從起點到終點,沿途所累加的權值的過程中要求不能小於等於0,那麼就需要在尋找最短路徑的鬆弛方法上做手腳。另外,最短路徑問題必須要考慮的就是,如果圖中存在環,怎麼辦?

如圖:
在這裏插入圖片描述

如果圖中的環,而且必須是正環,能夠對有效路徑中的某個節點進入,再從有效路徑中的某個節點出來,說明你可以在這個環中滾動無數圈,把分數「刷」到無限高,這樣就不怕路徑後面節點中出現負值了。

所以,一旦有這樣的環出現,直接就判斷爲可以走通即可。

可知,首先使用floyed算法對圖中的連通關係進行預處理,使用最短路徑中的SPFA算法,在鬆弛的過程中判斷進行鬆弛後的最短路徑值是否大於0,如果小於0說明走到此房間的分數是小於等於0的,不符合要求。

在圖中,由於是每個節點存在存在權值,所以需要將節點拆分成兩個節點加一條邊,原始頂點上的權值記爲邊上的權值即可。