spfa的算法思想(动态逼近法):
设立一个先进先出的队列q用来保存待优化的结点,优化时每次取出队首结点u,并且用u点当前的最短路径估计值对离开u点所指向的结点v进行松弛操作,如果v点的最短路径估计值有所调整,且v点不在当前的队列中,就将v点放入队尾。这样不断从队列中取出结点来进行松弛操作,直至队列空为止。
松弛操作的原理是著名的定理:“三角形两边之和大于第三边”,在信息学中我们叫它三角不等式。所谓对结点i,j进行松弛,就是判定是否dis[j]>dis[i]+w[i,j],如果该式成立则将dis[j]减小到dis[i]+w[i,j],否则不动。
下面举一个实例来说明SFFA算法是怎样进行的:
和广搜bfs的区别:
SPFA 在形式上和广度(宽度)优先搜索非常类似,不同的是bfs中一个点出了队列就不可能重新进入队列,但是SPFA中一个点可能在出队列之后再次被放入队列,也就是一个点改进过其它的点之后,过了一段时间可能本身被改进(重新入队),于是再次用来改进其它的点,这样反复迭代下去。
Wormholes
Time Limit: 2000MS | Memory Limit: 65536K | |
Total Submissions: 29971 | Accepted: 10844 |
Description
While exploring his many farms, Farmer John has discovered a number of amazing wormholes. A wormhole is very peculiar because it is a one-way path that delivers you to its destination at a time that is BEFORE you entered the wormhole! Each of FJ's farms comprises N (1 ≤ N ≤ 500) fields conveniently numbered 1..N, M (1 ≤ M ≤ 2500) paths, and W (1 ≤ W ≤ 200) wormholes.
As FJ is an avid time-traveling fan, he wants to do the following: start at some field, travel through some paths and wormholes, and return to the starting field a time before his initial departure. Perhaps he will be able to meet himself :) .
To help FJ find out whether this is possible or not, he will supply you with complete maps to F (1 ≤ F ≤ 5) of his farms. No paths will take longer than 10,000 seconds to travel and no wormhole can bring FJ back in time by more than 10,000 seconds.
Input
Line 1: A single integer, F. F farm descriptions follow.
Line 1 of each farm: Three space-separated integers respectively: N, M, and W
Lines 2..M+1 of each farm: Three space-separated numbers (S, E, T) that describe, respectively: a bidirectional path between S and E that requires T seconds to traverse. Two fields might be connected by more than one path.
Lines M+2..M+W+1 of each farm: Three space-separated numbers (S, E, T) that describe, respectively: A one way path from S to E that also moves the traveler back T seconds.
Output
Lines 1..F: For each farm, output "YES" if FJ can achieve his goal, otherwise output "NO" (do not include the quotes).
Sample Input
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
Sample Output
NO
YES
Hint
For farm 1, FJ cannot travel back in time.
For farm 2, FJ could travel back in time by the cycle 1->2->3->1, arriving back at his starting location 1 second before he leaves. He could start from anywhere on the cycle to accomplish this.
Source
解题思路:
农民有N块地,每块地看做一个节点,有m条普通的路(双向),连接着两个节点,从一端走到另一端需要w时间(权值),还有wh条特殊的单向路,也就是题意中的虫洞,虫洞也连接着两个节点,但虫洞是单向的,从起点走到终点需要w时间,但这个时间是负的,也就是题意中所说的时光倒流,比如 一个虫洞连接着s -> e,在s处假设时间为6,走虫洞时间为4,那么走过去时光倒流,走到e时时间就变为了2 (6 -4,也就是虫洞这条路的权值为 -4 ) ,好神奇。。。。问有没有这样一种情况,就是第二次走到某个节点的时间比第一次走到该节点所用的时间短(题中的Perhaps he will be able to meet himself,时光倒流的作用)。假设存在这种情况,那么以某节点为起点和终点一定存在着一个回路,这个回路的权值是负的,这样第二次所用的时间一定比第一次少。
个人理解:
spfa算法和dijkstra算法 非常相似 可以说是优化了的dijkstra 用queue 队列降低了时间复杂度 而且解决了dijkstra 不能录入负边权的问题。dijkstra算法 “目光短浅” 没办法看到“远方”的负边
代码:
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<iostream>
#include<queue>
#define N 0x3f3f3f3f
using namespace std;
int map[550][550];
int vis[550],s[550];//vis 记录当前这个点是否在队列里面 s 记录路径
int num[550];//记录当前点入队多少次。
int n,m,q;
int spfa()
{
memset(vis,0,sizeof(vis));
memset(s,N,sizeof(s));
memset(num,0,sizeof(num));
s[1]=0;
vis[1]=1;
num[1]=1;
queue<int>q;
q.push(1);
while(q.size())
{
int t=q.front();
q.pop();
vis[t]=0;//拿出之后就释放
for(int i=1;i<=n;i++)
{
if(s[t]+map[t][i]<s[i])
{
s[i]=s[t]+map[t][i];
if(vis[i]==0)
{
vis[i]=1;
num[i]++;
if(num[i]>n)
{
return 1;
}
q.push(i);
}
}
}
}
return 0;
}
int main()
{
int t;
cin>>t;
while(t--)
{
memset(map,N,sizeof(map));
cin>>n>>m>>q;
for(int i=1;i<=m;i++)
{
int a,b,c;
cin>>a>>b>>c;
if(map[a][b]>c)
map[a][b]=map[b][a]=c;
}
for(int i=1;i<=q;i++)
{
int a,b,c;
cin>>a>>b>>c;
if(map[a][b]>-c)
map[a][b]=-c;
}
if(spfa())
cout<<"YES"<<endl;
else
cout<<"NO"<<endl;
}
return 0;
}