题目链接:http://cstest.scu.edu.cn/soj/problem.action?id=2751
题目大意:
现在有一些无向边,走过每条边需要花费一定的时间ti。
另有一些有向的“虫洞”,钻过虫洞可以回到过去,让时间倒流ti。
问能否从某地出发,经过一定的虫洞和道路,在出发的时间之前回到原地。
算法:
我们不妨把道路看作正权边,
虫洞看作负权边,
以ti为权值建边。
不难看出,如果图上有负环,就可以实现在出发的时间之前回到原地。
SPFA寻找负环,只要在做最短路的时候判断是否存在某点的入队次数大于n即可。
因为没有负环的最短路,最多由n个点组成,
如果一个点被松弛了超过n次,那么必定是存在了负环。
另外,寻找负环的SPFA,可以用迭代深搜SPFA,详见姜碧野的《SPFA算法的优化及应用》,
如果像我一样比较懒,懒得改算法,那么用栈来代替FIFO队列保存顶点,也可以提高找负环的效率
需要注意的是,此题POJ上数据较弱,没有考虑图不连通的情况,请去SOJ测试。
其实考虑图不联通的情况也很简单,建一个虚拟起点,向每个点连一条边权为0的边,
当然在每个联通块做一次SPFA也是一样的,复杂度不变。
代码如下:
#include<stdio.h> #include<string.h> #define INF 0x3f3f3f3f int head[505],Q[505],inq[505],d[505],cot[505],E,flag; struct { int u,v,w,nxt; } edge[6000]; void addedge(int u,int v,int w) { edge[E].u=u; edge[E].v=v; edge[E].w=w; edge[E].nxt=head[u]; head[u]=E++; } int main() { int cas,n,m,k,u,v,w,front,rear; char c; while(~scanf("%d",&cas)) { while(cas--) { scanf("%d%d%d",&n,&m,&k); memset(head,-1,sizeof(head)); memset(d,INF,sizeof(d)); memset(inq,0,sizeof(inq)); memset(cot,0,sizeof(cot)); E=0; flag=1; for(int i=1; i<=n; i++) { addedge(0,i,0); } while(m--) { scanf("%d%d%d",&u,&v,&w); addedge(u,v,w); addedge(v,u,w); } while(k--) { scanf("%d%d%d",&u,&v,&w); addedge(u,v,-w); } front=rear=0; Q[rear++]=0; inq[0]=1; d[0]=0; while(front!=rear) { u=Q[front++]; front%=505; inq[u]=0; for(int i=head[u]; i!=-1; i=edge[i].nxt) { v=edge[i].v; if(d[v]>d[u]+edge[i].w) { d[v]=d[u]+edge[i].w; if(!inq[v]) { Q[rear++]=v; rear%=505; inq[v]=1; } cot[v]++; if(cot[v]==n+1) { flag=0; break; } } if(!flag) break; } if(!flag) break; } if(!flag) puts("YES"); else puts("NO"); } } return 0; }