开头说的一些话
大家所想要的sightseeing trip正在研究,预计一周更新。今天的题目前班里AC的人加上我一共只有6个大概是班里人数的1/3,但是从我个人看来这一道模板题还是具有一定的典型性的,特此更新今日的题解。
注:为了方便阅读,本文中的0x3f泛指无穷大,即int形的0x3f3f3f3f(4个3f)
正文
题目
输入数据给出一个有 N 个节点,M 条边的带权有向图。要求你写一个程序,判断这个有向图中是否存在负权回路。如果从一个点沿着某条路径出发,又回到了自己,而且所经过的边上的权和小于 0,就说这条路是一个负权回路。
如果存在负权回路,只输出一行 -1;如果不存在负权回路,再求出一个点 S 到每个点的最短路的长度。约定:S 到 S 的距离为 0,如果S 与这个点不连通,则输出 NoPath。
输入、输出数据和范例
输入:第一行三个正整数,分别为点数 N,边数 M,源点 S;
以下 M 行,每行三个整数 a, b, c,表示点 a, b 之间连有一条边,权值为 c。
输出:如果存在负权环,只输出一行 -1,否则按以下格式输出:
共 N 行,第 i 行描述 S 点到点 i 的最短路:
如果 S 与 i 不连通,输出 NoPath; 如果 i = S,输出 0。 其他情况输出 S 到 i 的最短路的长度。
样例输入:
6 8 1
1 3 4
1 2 6
3 4 -7
6 4 2
2 4 5
3 6 3
4 5 1
3 5 4
样例输出:
0
6
4
-3
-2
7
我个人的思考方向
众所周知,如果一个图里面存在负环,那么很大可能(记住,是很大可能,不是绝对)上这个图的全部或部分节点到源点是不存在最短路径的。距离可以无限小,松弛操作可以进行无限次。
对于这道题而言,题目的要求是只要有负环就要输出-1 。注意这里的-1可不是算法在求距离的时候遇到了负环才算负环的。如我自己出的下面一套数据,存在负环,但是按照一般SPFA是发现不了的(如果像我一样0x3f在松弛过程中没有被减的话)
5 4 1
2 3 -1
3 4 -1
4 5 -1
5 2 -1
如果我们用SPFA处理源点不能够到达的负环并且对于0x3f也和一般距离数据一样逐步往下减的话,我们其实也能找出源点所不能够达到的负环。但是,在dis数组中所认为的到达该点的距离也会跟着往下减。这样一来对我们最后是否输出为"no path"进行判定时就会遇到很大的麻烦——空凭数据我们可能无法知道我们的距离数据到底是从0x3f减下来的还是从源点一点一点算过来的,这样的场面,不得不说,十分尴尬。
在权衡之后我选择尽量保证数组中的0x3f不被破坏,也就使我的负环判定成为了一个需要攻克的点。
现在网上对于这种问题,有一种解决方案是多次设不同的源点去SPFA。但是为我自己想出来的解决方案是另开一个源点,使这个源点能够到达所有节点并且距离都是0。这样就不会漏搜了。如果发现了负环点,我们就输出-1,结束程序
走过了这一步,我们就得到了一个保证没有你负环的图。然后的解决方法就是套一个模板的事情啦!
按照往常的规矩,放出代码的注释。
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
/*
整体思路:SPFA算法,先构建一个原点保证每个节点都能够被到达,然后不断松弛
直到有一个点的入队次数大于等于节点数,如此就证明这个图存在负环,并且不存
在没有遍历到的负环的可能性。
如果没有负环,那么我们就用输入中给出的原点再进行一次SPFA,输出指定点到达
各个节点的距离。
邻接表用在这道题里面是一个不错的选择
*/
struct edge{
//存储一条边
int from;
int to;
int dist;
edge* next;
};
edge* ed[111111];//存储边的邻接表
int dis [1001];//距离
int dl[100000000];//SPFA队列
int rdt[1001];//入队的次数
int maxrdt = 0;//入队次数最多的点的入队次数
int tail,head;//队头和队尾
int pnum;//节点数
int ednum;