【校队排位赛#5 F】 POJ 3259 Wormholes SPFA +负权环

18 篇文章 0 订阅
17 篇文章 0 订阅
该博客讨论了如何利用SPFA算法解决一道关于时间旅行的ACM问题。题目要求找到从某个字段出发,通过路径和虫洞返回起点,使得总时间早于出发时间。作者分析了将问题转化为寻找是否存在一条路径使得路径和为负数,并介绍了SPFA算法处理负权环的优势。通过实例和代码,解释了SPFA算法的大致思路和实施过程。
摘要由CSDN通过智能技术生成

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.

题意,田间小路每条path会花费t时间,而存在单一通向的虫洞让你传送回某个路口并且时间倒流t1,问存不存在一种走法可以和过去的自己相遇

将题目问题模型化:

问能不能回到某个点,使得到这个点的时间比出发的时间还要早。如果把时间当做边的权值,那么问题就转变问存不存在一个出发点i使得回来的时候最短路径和小于0,d[i][i]<0不就说明比一开始的初始值(0)还要小,即是回到过去了吗?

最短路径问题

那么这个题目就可以转化为一个最短路径问题,能否存在一种最短路径使得d[i][i]<0?
一开始无脑用floyd,基本就是擦这超时的边过的。说明这个题解法并不是这样。还得优化。O(n3次方)不行,而dijkstra算法不能解决负权环问题,那么这个题改用什么算法呢?

SPFA算法

这个算法也是刚学,特地拿这个题练手。
大体思路:
我觉得这个算法和dijkstra的核心思路差不多,都是不断拓展外边,使得图联通的一种方法。只是这里并不是每一步取完最优路线就舍弃掉别的点,这里是像辐射状的将所有连接点都试探性地走一遍。然后遍历过的点因为边更新了(松弛了),又可以产生新的通路。不断循环此过程,如果存在负环,会出现d[i][i]小于0的情况,这时候它会一直循环下去,那么只用看任意一个点是否遍历过超过n次就知道有没有负权环了。
这里用队列实现上述流程,详见代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <vector>
#include <map>
#include <algorithm>
#include <queue>
#include <limits.h>
#define maxn 505
using namespace std;
vector<pair<int,int> >  D[maxn];    //建立pair对,用vector存,相当于二维数组  表示 i为结点向外延伸到y(first)的边(second)
int d[maxn] , vis[maxn], num[maxn];     //d[]表示到i原点距离,vis作是否在队列中的标志,num表示在队列中出现的次数
int n,m;

void init()     //初始化
{
     for(int i=1;i<=n;i++)
     D[i].clear();
     for(int i=1;i<=n;i++)
     vis[i] = 0;
     for(int i=1;i<=n;i++)
     d[i] = 1e9;
     for(int i=1;i<=n;i++)
     num[i] = 0;
}

bool SPFA()     //写成bool型,有负环返回真(有负环的话会一直进行循环)
{
        queue<int > q;
        q.push(1);      //先将第一个结点push进队列,因为图是联通的,哪个开始无所谓
        d[1] = 0;
        num[1] = 1;
        vis[1] = 1;
        while(!q.empty())
        {
              int now = q.front();      //取队头
              q.pop() , vis[now] = 0;       //pop掉,清除标记
              for(int i=0;i<D[now].size();i++)      //列出与当前结点相连接的点,看看是否可以使边松弛
              {
                    int v =D[now][i].first;
                  //  cout<<D[now][i].first<<' '<<now<<endl;
                    if(d[now] + D[now][i].second <d[v])     //原点到now点距离加上该边,小于原点到now连接点的距离,说明可以松弛
                    {
                            d[v] = d[now] + D[now][i].second;       //更新
                            if(vis[v])  continue;       
                            q.push(v);      //若不在队列中则push进来
                            num[v]++;       //奇数
                            vis[v] = 1;     //更新
                    }
                    if(num[v] >n) return true ;     //如果某个入队大于n次,说明已经形成负环进行无限循环了,已经可以return了
              }
        }
        return false;       //出循环了说明只有正环,无满足题意解
}

int main()      //以下就是读入和判断了
{
    int kase;
    cin>>kase;
    while(kase--)
    {
        int w;
        scanf("%d%d%d",&n,&m,&w);
        init();
        for(int i=1;i<=m;i++)
        {
            int x, y ,z;
            scanf("%d%d%d",&x,&y,&z);
            D[x].push_back(make_pair(y,z));
            D[y].push_back(make_pair(x,z));
        }
        for(int i=1;i<=w;i++)
        {
            int x, y ,z;
            scanf("%d%d%d",&x,&y,&z);
            D[x].push_back(make_pair(y,-z));
        }
        if(SPFA())
        cout<<"YES"<<endl;
        else
        cout<<"NO"<<endl;

    }
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值