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的,不符合要求。

在图中,由于是每个节点存在存在权值,所以需要将节点拆分成两个节点加一条边,原始顶点上的权值记为边上的权值即可。