链接
https://www.acwing.com/problem/content/906/
题目
农夫约翰在巡视他的众多农场时,发现了很多令人惊叹的虫洞。
虫洞非常奇特,它可以看作是一条 单向 路径,通过它可以使你回到过去的某个时刻(相对于你进入虫洞之前)。
农夫约翰的每个农场中包含N片田地,M条路径(双向)以及W个虫洞。
现在农夫约翰希望能够从农场中的某片田地出发,经过一些路径和虫洞回到过去,并在他的出发时刻之前赶到他的出发地。
他希望能够看到出发之前的自己。
请你判断一下约翰能否做到这一点。
下面我们将给你提供约翰拥有的农场数量F,以及每个农场的完整信息。
已知走过任何一条路径所花费的时间都不超过10000秒,任何虫洞将他带回的时间都不会超过10000秒。
输入格式
第一行包含整数F,表示约翰共有F个农场。
对于每个农场,第一行包含三个整数N,M,W。
接下来M行,每行包含三个整数S,E,T,表示田地S和E之间存在一条路径,经过这条路径所花的时间为T。
再接下来W行,每行包含三个整数S,E,T,表示存在一条从田地S走到田地E的虫洞,走过这条虫洞,可以回到T秒之间。
输出格式
输出共F行,每行输出一个结果。
如果约翰能够在出发时刻之前回到出发地,则输出“YES”,否则输出“NO”。
数据范围
1≤F≤5
1≤N≤500,
1≤M≤2500,
1≤W≤200,
1≤T≤10000,
1≤S,E≤N
输入样例:
2
3 3 1
1 2 2
1 3 4
2 3 1
3 1 3
3 2 1
1 2 3
2 3 4
3 1 8
输出样例:
NO
YES
思路
虫洞可以理解为负权边,问题就是判断图中是否存在负环。那么就要用spfa算法。
spfa求负环常用方法两种:
1.(cnt[u])记录节点入队的次数,当某个节点入队次数n次,说明该节点在负环当中。
2.(cnt[u])记录节点所在的最短路路径中的节点数,如果节点数大于n,说明该节点在负环中。
一般来说用第二种的效率更高。
众所周知,spfa随便都能被卡,然后还有各种优化:
1.将spfa的队列改成栈
2.如果spfa被卡,说明图中有大概率存在负环,需要加入一个变量表示经验值,记录所有节点的入队次数和(count),当(count>2n)时表示发现负环
3.SLF优化,视情况可以用一下
4.当求在整个图中是否存在负环,需要将所有点加入队列,所有点的距离可以初始化为0(不算优化
以及:如果出题人能够写出在有负权图中比spfa跑的更快的算法,请随便ban!
这道题中判断整个图中的负环,所以一开始需要将说有点加入队列中再跑spfa。
代码
#include<bits/stdc++.h>
using namespace std;
const int N=510,M=6000;
int h[N],e[M],w[M],nex[M],idx,n;
int d[N],q[N],st[N],cnt[N];
bool spfa(){
int hh=0,tt=0;
for(int i=1;i<=n;++i){
q[tt++]=i;
st[i]=1;
d[i]=0;
cnt[i]=0;
}
while(hh!=tt){
int u=q[--tt];//栈优化
//int u=q[hh++];
//if(hh==N) hh=0;
st[u]=0;
for(int i=h[u];~i;i=nex[i]){
int v=e[i];
if(d[v]>d[u]+w[i]){
d[v]=d[u]+w[i];
cnt[v]=cnt[u]+1;
if(cnt[v]>=n) return false;
if(!st[v]){
q[tt++]=v;
if(tt==N) tt=0;
st[v]=1;
}
}
}
}
return true;
}
void add(int u,int v,int t){
e[idx]=v;
w[idx]=t;
nex[idx]=h[u];
h[u]=idx++;
}
int main(){
int m1,m2,T;
scanf("%d",&T);
while(T--){
memset(h,-1,sizeof h);idx=0;
scanf("%d%d%d",&n,&m1,&m2);
while(m1--){
int x,y,t;
scanf("%d%d%d",&x,&y,&t);
add(x,y,t);
add(y,x,t);
}
while(m2--){
int x,y,t;
scanf("%d%d%d",&x,&y,&t);
add(x,y,-t);
}
if(spfa()) puts("NO");
else puts("YES");
}
return 0;
}