在这之前我对spfa判负环一直有一定的误解,今天想借着这道题把一些容易出错的点说出来,也希望大家做这类题目时能少走一些弯路。
题意:
输入数据给出一个有 个节点, 条边的带权有向图。要求你写一个程序,判断这个有向图中是否存在负权回路。如果从一个点沿着某条路径出发,又回到了自己,而且所经过的边上的权和小于 ,就说这条路是一个负权回路。
如果存在负权回路,只输出一行 ;如果不存在负权回路,再求出一个点 到每个点的最短路的长度。约定: 到 的距离为 ,如果 与这个点不连通,则输出 NoPath
。
输入格式
第一行三个正整数,分别为点数 ,边数 ,源点 ;
以下 行,每行三个整数 ,表示点 之间连有一条边,权值为 。
输出格式
如果存在负权环,只输出一行 ,否则按以下格式输出:
共 行,第 行描述 点到点 的最短路:
- 如果 与 不连通,输出
NoPath
; - 如果 ,输出 。
- 其他情况输出 到 的最短路的长度。
样例
输入
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
这道题目看完后,我的第一思路就是直接写一个spfa判负环,在判负环过程中顺便求出源点到所有点的最短距离。之前判负环都是先把所有点都入队列,然后在进行cnt数组的更新,当发现某一个点的cnt数组值大于等于n时就确定是有负环了,但之所以要把所有点都加进去是因为我们一开始无法保证图是连通的,也就是说无法确定源点能不能到达负环,而后来学习了超级源点,我就想着建立一个超级源点,然后用超级源点与其余所有点连一条边,这样就实现了图的连通,其实这样想是没有问题的,关键就在于我们之前判负环的做法是先把图中所有点都入队列,这样判负环的话d[]数组是没有必要初始化的,而我们如果用超级源点使图连通的话d[]数组就需要满足一定的条件了,我们首先加入的是超级源点,然后用超级源点把其余点入队列,但是入队列是有一定条件的啊,如果不满足条件的话我们的超级源点不就白加了么?下面我来具体分析一下如何初始化d[]数组使得超级源点能够使其他点入队列。
(1)我们判的是正环,这样我们跑的是最长路,更新条件是 d[i]<d[j]+w j一开始就是我们所设立的超级源点,所以我们要把d[]数组初始化为负无穷大才能够实现一开始就让所有点入队的目标
(2)我们判的是负环,这样我们就需要跑最短路了,更新条件是 d[i]>d[j]+w j一开始还是我们所设立的超级源点,所以我们要把d[]数组初始化为正无穷大才能够实现一开始就让所有点入队的目标
这两点真的非常容易出错,希望读者能够好好理解一下
还有一个需要注意的点就是我们在用spfa求负环的过程中是没有办法求最短路的,举个最简单的例子,倘若我们一开始的图是一个不连通的图,有两个子图,但是我们判负环是把所有点都入队列了,所以另一个不包含源点的子图中的点也可能被其他点更新,所以我们只能再用源点求一次最短路
这道题目需要注意的点我已经在上面都提到了,下面就直接上代码:
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<queue>
using namespace std;
const int N=3e5+10;
int h[N],d[N],w[N],ne[N],cnt[N],e[N],idx;
bool vis[N];
void add(int x,int y,int z)
{
e[idx]=y;
w[idx]=z;
ne[idx]=h[x];
h[x]=idx++;
}
int n,m,s;
bool spfa()//用于判负环
{
queue<int> q;
memset(vis,false,sizeof vis);
memset(cnt,0,sizeof cnt);
//如果我们一开始把所有点入队列,那么d数组就不用初始化,而我们设立超级源点,为了使超级源点能够更新所有点,我们必须根据不同的情况对d数组进行初始化
memset(d,0x3f,sizeof d);
d[s]=0;
for(int i=1;i<=n;i++) vis[i]=true,q.push(i);
while(q.size())
{
int begin=q.front();
q.pop();
vis[begin]=false;
for(int i=h[begin];i!=-1;i=ne[i])
{
int j=e[i];
if(d[j]>d[begin]+w[i])
{
d[j]=d[begin]+w[i];
cnt[j]=cnt[begin]+1;
if(cnt[j]>=n) return true;
if(!vis[j])
{
vis[j]=true;
q.push(j);
}
}
}
}
return false;
}
void tspfa()//用于求最短路
{
queue<int> q;
memset(vis,false,sizeof vis);
memset(d,0x3f,sizeof d);
d[s]=0;
q.push(s);
while(q.size())
{
int begin=q.front();
q.pop();
vis[begin]=false;
for(int i=h[begin];i!=-1;i=ne[i])
{
int j=e[i];
if(d[j]>d[begin]+w[i])
{
d[j]=d[begin]+w[i];
if(!vis[j])
{
vis[j]=true;
q.push(j);
}
}
}
}
}
int main()
{
memset(h,-1,sizeof h);
cin>>n>>m>>s;
int a,b,c;
for(int i=1;i<=m;i++)
{
scanf("%d%d%d",&a,&b,&c);
add(a,b,c);
}
if(spfa()) puts("-1");
else
{
tspfa();
for(int i=1;i<=n;i++)
{
if(d[i]==0x3f3f3f3f) puts("NoPath");
else printf("%d\n",d[i]);
}
}
return 0;
}
这两点还是挺容易出错的,希望读者能够停下来想一下,说不定在下次做题的时候就碰到了,不要像我一样,wa了好几次才过。
如果大家还对spfa求最短路和负环有什么疑问,欢迎在评论区里面分享,我如果能够明白,一定会做出解答!