HDU 1317 XYZZY

传送门

最短路。涉及到了把求最长路转化为求最短路,还有bellman_ford判断负环(可从起点到,也应可到终点的负环)的问题。这道题比较综合。

有最多100个房间,其中有一个是起点有一个是终点,每个房间都有一个能量值([-100,100]),单向的门(有向图)连接两个房间。有一个人从起点出发(自身携带100能量),每到一个房间就获得这个房间的能量(房间的能量不会减少,可以多次获得同一个房间的能量),这个人在途中只要能量小于等于0就挂了(至于是小于等于0还是小于0看了样例才知道),起点房间和终点房间的能量都是0,问你这个人能不能到终点。

首先要把房间和单向门转化为点和边,但是能量是依附在点上的,怎么办?我们把每个点的能量转化到这个点的所有后驱边上作为边权(4月12日更新:这样做的前提是有向图)(终点没有后驱,正好其能量也是0,不用再添一个虚拟终点),而初始的100能量值可以考虑有一个虚拟起点,这个虚拟起点到起点有一条100的边(除此之外没有任何边连接虚拟起点),这样我们可以直接将将起点的dis[]初始化为100,然后还是N次(dijkstra)或N-1次(bellman_ford)循环就行。
(这初始的100能量不能重复获得,所以不能把这个当做起点的后驱边的权值)

上面完成了建图,这道题还有一个根本问题,可以看出他是要你求的起点到终点的路的最大值能不能大于0。我们必须转化为最短路问题,把上述所有边权符号反转,以及起点初始为dis[1] = -100,这样,我们求的是起点到终点的路的最小值能不能小于0

还有一个问题,这道题的意思是中途每到一个房间都要判断!不行了直接挂!中途不满足条件然后到终点了再满足条件是不行的! 所以必须每枚举一条边都要判断中介点加上这条边权值是不是大于等于0了,如果是则直接continue,绝对不能让他传递下去(如果这条边不能到终点,那计算这条边下一个点没有任何用;如果能到,那么中间这里一定非法了,到了也无效)。

还有一个重要问题,边权有正有负,不能用dijkstra了,只能bellman_ford或者spfa。但是!考虑负环,如果有一个起点可到的负环(起点到负环的过程必须合法,满足那个条件),而且如果这个负环能到终点(这里只用判断是否连通,不用考虑合不合法,因为就算这个负环先走一遍然后不能合法走到终点,但是我可以绕着这个负环走一亿遍,怕不怕?),那么结果直接可行。
所以上述这个问题又涉及两个子问题,怎么判负环和怎么判连通。注意,我觉得这道题必须把所有起点可到的负环都判定一遍(判定和终点连不连通),所以不能用spfa了只能bellman_ford(虽然spfa也能过,但那是数据不严谨的问题)。至于判断连通性问题,可以刚开始先全局floyd一遍(这是个固定写法,务必掌握),也可以临时写个dfs

#include <iostream>
#include <algorithm>
#include <vector>
#include <cstring>
using namespace std;

const int INF = 1e9;
const int MAXN = 102;
int N;

vector<pair<int, int>> v[MAXN];
int dis[MAXN];
bool to[MAXN][MAXN];

void init()
{
	for (int i = 1; i <= N; i++)
		v[i].clear();
	fill(dis + 1, dis + N + 1, INF);
	memset(to, 0, sizeof to);
}

bool bellman_ford(int s)
{
	dis[s] = -100;                    //
	for (int i = 0; i < N - 1; i++)
	{
		for (int j = 1; j <= N; j++)
		{
			for (int k = 0; k < v[j].size(); k++)
			{
				pair<int, int>& p = v[j][k];
				if (dis[j] + p.second >= 0)      // 1
					continue;
				if (dis[j] + p.second < dis[p.first])
					dis[p.first] = dis[j] + p.second;
			}
		}
	}
	for (int j = 1; j <= N; j++)
	{
		for (int k = 0; k < v[j].size(); k++)
		{
			pair<int, int>& p = v[j][k];
			if (dis[j] + p.second >= 0)          // 2     1 2这两处判断必须都要!
				continue;
			if (dis[j] + p.second < dis[p.first] && to[j][N])
				return true;                     // 有 起点可达 且 也可达终点 的负环
		}
	}
	return dis[N] < 0;                           // 这里换成 dis[N]!=INF 也可以
}

void floyd()
{
	for (int k = 1; k <= N; k++)
	{
		for (int i = 1; i <= N; i++)
		{
			for (int j = 1; j <= N; j++)
				to[i][j] = to[i][j] || (to[i][k] && to[k][j]);
		}
	}
}

int main()
{
	int a, b, c;
	for (; ~scanf("%d", &N);)
	{
		if (N == -1) return 0;
		init();
		for (int i = 1; i <= N; i++)
		{
			scanf("%d%d", &a, &b);
			for (; b--;)
			{
				scanf("%d", &c);
				v[i].push_back(make_pair(c, -a));          // 全部反转边权符号
				to[i][c] = to[i][i] = true;                // 加上一个 to[i][i] = true,更稳当一点
			}
		}

		floyd();
		if (!to[1][N])                // 提前判断排除一下,因为正好刚计算完to,不写也没事,后面能揪出来
		{
			printf("hopeless\n");
			continue;
		}
		printf("%s\n", bellman_ford(1) ? "winnable" : "hopeless");
	}

	return 0;
}

转载于:https://www.cnblogs.com/CrossingOver/p/10704864.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值