题目链接:http://poj.org/problem?id=3259
题意:一个农场主,有n个农场,农场之间有m条双向路,每条路有花费时间权值,有w个虫洞以路的形式给出,权值为可以回到多久之前。
思路:虫洞可以看成是一条负权路,问题就转化成求一个图中是否存在负权回路;
1.bellman_ford算法
求距离用模板就行了,怎么判断有没有负权回路?
判断边集E中的每一条边的两个端点是否收敛。
通俗点就是判断每条边两个顶点到源点的距离差是否就是边值。
如果差值都是边值很明显无负权回路,反正则有。
即有负权回路时,存在d[边的终点]>d[边的起点]+边的权值。why?
因为有负权回路时,遍历最后一遍是,环的起点更新变的更小,而与它连边的点没有变。
没懂?看下面例子就知道了。
比例:有三条边:1-->2 权值为1, 2-->3权值为2, 3-->1权值为-4。构成负权回路,bellman_ford算法会遍历n-1次(这里为2次)全部的边
假设起点为1,终点为3,d[]表示到点1的距离。
第一遍: d[1]=0,d[2]=1, d[3]=3, 遍历3-->1时 d[1]=-1;
第一遍: d[1]=-1, d[2]=0, d[3]=2, 遍历3-->1时 d[1]=-2;
可以看出环的起点1的距离变小了,但后面的边没来得及更新变得更小,所以使得d[2]=0>d[1]+1=-1。
代码:
#include<iostream> #include<algorithm> #include<cstdio> #include<cstring> #define inf 0x3f3f3f3f using namespace std; typedef long long ll; const int maxn=6000; struct node{ int next,from,to,w; }edge[maxn]; int head[maxn],d[maxn],cnt,n,m,z; void init() { memset(head,-1,sizeof(head)); memset(d,0x3f,sizeof(d)); cnt=0; } void add(int u,int v,int w)//前向星连边 { edge[cnt].from=u; edge[cnt].to=v; edge[cnt].w=w; edge[cnt].next=head[u]; head[u]=cnt++; } bool bellmanford() { d[1]=0; for(int i=0;i<n;i++)//板子 { for(int j=0;j<cnt;j++) if(d[edge[j].to]>d[edge[j].from]+edge[j].w) d[edge[j].to]=d[edge[j].from]+edge[j].w; } for(int i=0;i<cnt;i++)//判断是否存在负权回路 if(d[edge[i].to]>d[edge[i].from]+edge[i].w) return true; return false; } int main() { int t; scanf("%d",&t); while(t--) { init(); scanf("%d%d%d",&n,&m,&z); int a,b,c; for(int i=0;i<m;i++) { scanf("%d%d%d",&a,&b,&c); add(a,b,c); add(b,a,c); } for(int i=0;i<z;i++)//虫洞权值变负 { scanf("%d%d%d",&a,&b,&c); add(a,b,-c); } if(bellmanford()) printf("YES "); else printf("NO "); } return 0; }
2.spfa算法
那spfa怎么求负权回路呢?这个就比上面简单了。
我们知道spfa如果碰到负权回路就会一直死循环,因为他会一直更新最短的路,有负权回路,环里的路权值会越来越小。
如果没有负权回路,两点间有最短路的话,那么每个结点最多经过一次。也就是说,这条路不超过n-1条边。
所以只要判断每个点入队的次数,次数>n-1就有负权回路了。
代码:
#include<iostream> #include<algorithm> #include<cstdio> #include<cstring> #include<queue> #include<map> #include<vector> #define inf 0x3f3f3f3f using namespace std; typedef long long ll; const int maxn=6000; struct node{ int to,next,w; }edge[maxn]; int head[maxn],d[maxn],vis[maxn],num[maxn],cnt,n,m,z; void add(int u,int v,int w) { edge[cnt].to=v; edge[cnt].w=w; edge[cnt].next=head[u]; head[u]=cnt++; } bool spfa()//spf板子 { memset(vis,0,sizeof(vis)); memset(num,0,sizeof(num)); queue<int> q; vis[1]=1; d[1]=0; num[1]++; q.push(1); while(!q.empty()) { int u=q.front(); q.pop(); vis[u]=0; for(int i=head[u];i!=-1;i=edge[i].next) { int v=edge[i].to; if(d[v]>d[u]+edge[i].w) { d[v]=d[u]+edge[i].w; if(!vis[v]) { vis[v]=1; q.push(v); num[v]++; if(num[v]>=n)//判断入队次数,是否构成负权回路。 return true; } } } } return false; } int main() { int t; scanf("%d",&t); while(t--) { scanf("%d%d%d",&n,&m,&z); cnt=0; memset(head,-1,sizeof(head)); for(int i=1;i<=n;i++) d[i]=inf; int a,b,c; for(int i=0;i<m;i++) { scanf("%d%d%d",&a,&b,&c); add(a,b,c); add(b,a,c); } for(int i=0;i<z;i++) { scanf("%d%d%d",&a,&b,&c); add(a,b,-c);//虫洞权值变负 } if(spfa()) printf("YES "); else printf("NO "); } return 0; }