题目链接: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;
}
本文介绍如何使用SPFA算法解决时间旅行问题,通过构建正权边和负权边,寻找图中的负环,判断是否能在出发时间之前回到原地。
604

被折叠的 条评论
为什么被折叠?



