Bellman-Ford || SPFA :Wormholes

Farmer John 在探索多个农场时发现了一系列奇特的虫洞,这些虫洞能够将他送到进入之前的时间点。通过详细的农场地图,FJ 设计了一套算法来确定是否有可能回到出发点之前的时间点,实现时间旅行的梦想。本文详细介绍了这个问题的背景、输入输出格式、以及解决策略,包括使用 Floyd 算法、Bellman-Ford 算法和 SPFA 算法进行求解的过程。
E - Wormholes
Time Limit:2000MS    Memory Limit:65536KB    64bit IO Format:%I64d & %I64u

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.


这题第一印象得用Floyd做,死活过不了

转战Bellman-Ford和SPFA,数据有点弱,只用把第一点当做源点即可

还有就是一定要记得写好每条初始化语句,wa了无数次才查出来...


#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>

using namespace std;

const int INF = 1<<31-1;

int n, m, w;
int d[550];
int cnt;

struct edge{
    int u, v, w;
}e[6000];

bool bellman(){
    int i, j, k;
    for(i = 1; i <= n; i ++){
        d[i] = INF;
    }
    d[1] = 0;
    for(k = 0; k < n-1; k ++){
        int flag = 1;// 判断是否松弛完毕
        for(i = 1; i <= cnt; i ++){
            int x = e[i].u;
            int y = e[i].v;
            if(d[x] != INF)
                if(d[y] > d[x] + e[i].w){
                    d[y] = d[x] + e[i].w;
                    flag = 0;
                }
        }
        if(flag)
            break;
    }

    for(i = 1; i <= cnt; i ++){
        if(d[e[i].v] > d[e[i].u] + e[i].w)
            return true; //存在负权回路
    }
    return false;
}

int main(){
    int f;
    int i;
    cin >> f;
    while(f -- ){
        cnt = 0;
        scanf("%d %d %d", &n, &m, &w);
        for(i = 1; i <= m; i ++){
            cnt ++;
            scanf("%d %d %d", &e[cnt].u, &e[cnt].v, &e[cnt].w);
            cnt ++;
            e[cnt].u = e[cnt-1].v;
            e[cnt].v = e[cnt-1].u;
            e[cnt].w = e[cnt-1].w;
        }
        for(i = 1; i <= w; i ++){
            cnt ++;
            scanf("%d %d %d", &e[cnt].u, &e[cnt].v, &e[cnt].w);
            e[cnt].w = -e[cnt].w;
        }
        if(bellman())
            printf("YES\n");
        else
            printf("NO\n");
    }
    return 0;
}

#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <queue>

using namespace std;

const int INF = 1<<31-1;

int n, m, ww;
int d[55000];
int cnt;

struct node{
    int to;
    int next;//每次插边的时候是插在已插的边前面,所以已插的边是新边的next
    int weight;
}e[60000];

int lastshow[40000], num[40000];
bool inqueue[40000];
queue<int>q;

void insert(int a, int b, int w){
    e[++cnt].to = b;
    e[cnt].weight = w;
    e[cnt].next = lastshow[a];
    lastshow[a] = cnt;//lastshow记录一个点上次作为起点的边的序号,所以一条边的next是它的起点的lastshow值
}

bool spfa(){
    q.push(1);
    num[1] ++;
    while(!q.empty()){
        int x = q.front();
        q.pop();
        inqueue[x] = false;
        int id = lastshow[x];
        while(id != -1){
            if(d[x] < INF && d[e[id].to] > e[id].weight + d[x]){
                d[e[id].to] = e[id].weight + d[x];
                if(!inqueue[e[id].to]){
                    inqueue[e[id].to] = true;
                    q.push(e[id].to);
                }
                num[id]++;
                if( num[id] > n )//如果入队的次数超过总数,说明存在回路
                    return true;
            }
            id = e[id].next;
        }
    }
    return false;
}

int main(){
    int f;
    int i;
    int a, b, w;
    cin >> f;
    while(f -- ){
        cnt = 0;
        scanf("%d %d %d", &n, &m, &ww);
        memset(lastshow,-1,sizeof(lastshow));
        memset(inqueue, false, sizeof(inqueue));
        memset(num, 0, sizeof(num));
        for(i = 1; i <= n; ++ i)
            d[i]=INF;
        d[1]=0;
        cnt=0;
        while(!q.empty())
        {
            q.pop();
        }
        for(i = 1; i <= m; i ++){
            scanf("%d %d %d", &a, &b, &w);
            insert(a, b, w);
            insert(b, a, w);
        }
        for(i = 1; i <= ww; i ++){
            scanf("%d %d %d", &a, &b, &w);
            insert(a, b, -w);
        }
        if(spfa())
            printf("YES\n");
        else
            printf("NO\n");
    }
    return 0;
}



<think>嗯,用户现在想了解Bellman-FordSPFA算法的区别。之前讨论过连通分量算法,现在突然转向最短路径问题,可能是学习进度推进到图论的新章节了。用户特意提到站内引用,说明ta手头有参考资料但需要更清晰的对比分析。 从引用内容看,用户提供的资料其实有些错位——引用[1][2][3]主要讲Bellman-FordSPFA,但引用[4]突然跳到Floyd算法心得,而标题里提到的"最短路径Bellman-Ford算法"等引用内容实际并未出现。不过核心信息足够用:引用[1]展示Bellman-Ford的伪代码,引用[2]说明SPFA是队列优化的Bellman-Ford,引用[3]指出其局限性和改进方向。 用户应该刚学完Dijkstra算法(引用[4]提到"学完最短路径需要用的几种著名算法"),现在接触带负权边的情况。ta的提问很精准——没有问基础原理而是直接要求对比差异,说明至少看过伪代码。但可能被这两个相似算法的关系搞糊涂了,需要厘清SPFA到底"优化"在哪里。 需要强调三个关键差异点:1)迭代方式(全边扫描 vs 动态更新)2)时间复杂度差异 3)负环检测的特殊处理。表格对比会很直观,但要注意用户提供的引用里提到SPFA时间复杂度是$O(kE)$(k平均为2),这点必须标注清楚,因为教材常写$O(VE)$可能让用户困惑。 突然想到用户可能混淆了"优化"的含义——SPFA不是改变Bellman-Ford的理论复杂度,而是通过减少无效松弛来提升实际效率。这个点要在回答中用实验数据案例说明(比如网格图退化情况)。另外必须警告SPFA的最坏情况,有些ACM选手会忽略这点导致比赛TLE。 最后补充负环检测的细节差异,用户给的引用[3]提到"负权环无法处理"但没具体说,这里要明确Bellman-Ford通过额外迭代检测,SPFA则用入队次数。实用建议部分很重要——用户肯定是想写代码,要给出算法选择决策树:有负权吗?→图稀疏吗?→需要检测负环吗?... </think> Bellman-Ford算法与SPFA算法均用于解决**带负权边的单源最短路径问题**,二者的核心区别在于**迭代策略和效率优化**。以下是详细对比分析: --- ### **1. Bellman-Ford算法** #### **算法思想** 通过$V-1$轮($V$为顶点数)全边松弛操作,逐步逼近最短路径。每轮遍历**所有边**,更新源点到各顶点的距离。 **伪代码**: ```python for i in range(V-1): # 进行V-1轮迭代 for each edge u->v: # 遍历所有边 if d[u] + w(u,v) < d[v]: d[v] = d[u] + w(u,v) ``` #### **关键特性** - **时间复杂度**:$O(V \cdot E)$ - **负环检测**:完成$V-1$轮后,**再执行一轮全边松弛**。若仍有距离更新,说明存在负权环[^3]。 - **优点**:实现简单,能处理负权边并检测负环。 - **缺点**:大量冗余计算(即使距离未更新仍遍历所有边),效率低[^1][^3]。 --- ### **2. SPFA算法(队列优化的Bellman-Ford)** #### **算法思想** 基于队列动态管理待松弛顶点,仅当顶点距离更新时才将其邻边加入松弛队列。 **伪代码**: ```python queue.push(source) # 源点入队 while queue not empty: u = queue.pop() for each neighbor v of u: if d[u] + w(u,v) < d[v]: d[v] = d[u] + w(u,v) if v not in queue: # 若v不在队列中则入队 queue.push(v) ``` #### **关键特性** - **时间复杂度**:**平均$O(kE)$**($k$为常数,通常$k \approx 2$),最坏$O(V \cdot E)$[^2][^4]。 - **负环检测**:记录顶点**入队次数**。若某顶点入队次数$\geq V$,说明存在负环[^3]。 - **优点**:**大幅减少冗余计算**,平均效率显著优于Bellman-Ford- **缺点**:**最坏情况效率与Bellman-Ford相同**(如网格图),且队列操作增加常数开销[^2]。 --- ### **3. 核心区别对比** | **特性** | **Bellman-Ford** | **SPFA** | |------------------|-----------------------------|------------------------------| | **迭代方式** | 固定$V-1$轮全边扫描 | 队列动态管理待松弛顶点 | | **时间复杂度** | $O(V \cdot E)$ | 平均$O(kE)$,最坏$O(V \cdot E)$ | | **空间复杂度** | $O(V)$ | $O(V + E)$(需存储队列和邻接表) | | **负环检测** | 额外一轮全边松弛 | 统计顶点入队次数 | | **实际效率** | 低(大量无效松弛) | 高(仅松弛可能更新的边) | | **适用场景** | 稠密图或需简单实现时 | 稀疏图且需高效处理负权边时 | --- ### **4. 为什么SPFA更高效?** SPFA通过队列避免无效松弛: - 只有**距离被更新的顶点**才会触发其邻边的松弛操作。 - 例如:若顶点$u$的距离未变化,则无需检查$u \to v$的边[^2]。 > ⚠️ **注意**:SPFA在**特定图结构(如网格图)中会退化**为$O(V \cdot E)$,此时Bellman-Ford的稳定性更优。 --- ### **5. 选择建议** - **优先SPFA**:稀疏图且无负环嫌疑时(如网络路由优化)。 - **使用Bellman-Ford**:需严格检测负环,或图结构易导致SPFA退化时。 - **替代方案**:无负权边时,**Dijkstra算法**($O(E \log V)$)更高效。 --- ### **
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值