题目描述:
农夫约翰在巡视他的众多农场时,发现了很多令人惊叹的虫洞。
虫洞非常奇特,它可以看作是一条 单向 路径,通过它可以使你回到过去的某个时刻(相对于你进入虫洞之前)。
农夫约翰的每个农场中包含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求负环的裸题,题目意思大概是农村中有若干个田地,通过田地间的某些边需要消耗一定的时间,而一些边上存在虫洞,走过去便可以回到过去,也就相当于负权边。问从某片田地出发能不能在出发时刻前回到起点?注意这里的问法意味着我们可以任选起点,只要从这点出发最后在出发之前回到该点即可,也就是说只要图中存在负权回路就可以了。之前在AcWing 852 spfa判断负环里已经介绍了spfa算法求负环的解法,这里再简单的复习下。
负权回路是指图中的某个回路中所有边权之和是负数,意味着只要沿着这个回路一直走,所走的总路程就会越来越小。我们知道,BellmanFord算法是遍历所有的边,一轮轮去松弛相邻的点,最多n-1轮后,起点就可以松弛到终点了。而spfa算法,只将这轮松弛过的点加入到队列,因为只有上一轮被松弛的点才可能继续去松弛周围的点。没有负权回路的话,从起点到终点的最短路径至多只会经过n - 1条边,如果从起点经过了不少于n条边还在松弛周围的点,则说明肯定存在负权回路。可以用cnt[i]表示松弛到i点已经经过的边数,一旦出现cnt的值达到n的点,就说明存在负权回路了。
之前实现spfa算法使用的是stl里现成的queue,这里采用数组模拟队列,队列里至多有n个点同时在队列里,而每个点可能不止入队一次,所以可以使用循环队列,当hh和tt到达队列上限N的时候就归零即可。循环队列的终止条件就是hh == tt时,因为队列空间循环使用的缘故,就不能使用hh < tt作为循环条件了。
还有个值得注意的点是,spfa算法求最短路时需要将距离数组初始化为无穷大,只给起点的距离赋初值0,那是因为我们需要知道准确的路径长度,而判断负环是否存在的问题我们不关心具体回路的长度,只关心它是不是负数,所以初值可以一律设置为0,当遇见负权边的时候,即使d是0也会被松弛的。另外需要注意的是多组测试用例时,不要忘了给链表头数组h赋初值-1,以及给边的序号idx赋初值0,以防止数组越界。具体实现见代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 505,M = 5205;
int idx,h[N],e[M],w[M],ne[M];
int n,m1,m2,d[N],cnt[N],q[N];
bool st[N];
void add(int a,int b,int c){
e[idx] = b,w[idx] = c,ne[idx] = h[a],h[a] = idx++;
}
bool spfa(){
memset(st,false,sizeof st);
memset(d,0,sizeof d);
memset(cnt,0,sizeof cnt);
int hh = 0,tt = 0;
for(int i = 1;i <= n;i++){
q[tt++] = i;
st[i] = true;
}
while(hh != tt){
int u = q[hh++];
if(hh == N) hh = 0;
st[u] = false;
for(int i = h[u];~i;i = ne[i]){
int j = e[i];
if(d[u] + w[i] < d[j]){
d[j] = d[u] + w[i];
cnt[j] = cnt[u] + 1;
if(cnt[j] >= n) return true;
if(!st[j]){
q[tt++] = j;
if(tt == N) tt = 0;
st[j] = true;
}
}
}
}
return false;
}
int main(){
int T;
scanf("%d",&T);
while(T--){
scanf("%d%d%d",&n,&m1,&m2);
int a,b,c;
memset(h,-1,sizeof h);
idx = 0;
for(int i = 0;i < m1;i++){
scanf("%d%d%d",&a,&b,&c);
add(a,b,c),add(b,a,c);
}
for(int i = 0;i < m2;i++){
scanf("%d%d%d",&a,&b,&c);
add(a,b,-c);
}
if(spfa()) puts("YES");
else puts("NO");
}
return 0;
}