codevs 1269 匈牙利游戏 SPFA+次短路

匈牙利游戏之最短路径
本文介绍了一个趣味性的编程问题——匈牙利游戏,参赛者需要在布达佩斯复杂的单向街道网络中寻找从起点到终点的严格次短路径。文章详细解释了问题背景、输入输出要求,并提供了一段实现SPFA算法的C++代码示例。

题目描述 Description
Welcome to the Hungary Games! The streets of Budapest form a twisted network of one-way streets.

欢迎来到匈牙利游戏!布达佩斯(匈牙利首都)的街道形成了一个弯曲的单向网络。

You have been forced to join a race as part of a “Reality TV” show
where you race through these streets, starting at the Sz´echenyi
thermal bath (s for short) and ending at the Tomb of G¨ ul Baba (t for
short).

你被强制要求参加一个赛跑作为一个TV秀的一部分节目,比赛中你需要穿越这些街道,从s开始,到t结束。

Naturally, you want to complete the race as quickly as possible,
because you will get more promo- tional contracts the better you
perform.

很自然的,你想要尽快的完成比赛,因为你的比赛完成的越好,你就能得到更多的商业促销合同。

However, there is a catch: any person who is smart enough to take a shortest s-t route will be thrown into the P´alv¨olgyi cave system and kept as a national treasure. You would like to avoid this fate, but still be as fast as possible. Write a program that computes a strictly-second-shortest s-t route.

但是,有一个需要了解的是,如果有人过于聪明找到从s到t的最短路线,那么他就被扔到国家极品人类保护系统中作为一个国家宝藏收藏起来。你显然要避免这种事情的发生,但是也想越快越好。写一个程序来计算一个从s到t的严格次短路线吧。

Sometimes the strictly-second-shortest route visits some nodes more than once; see Sample Input 2 for an example.

有的时候,严格次短路线可能访问某些节点不止一次。样例2是一个例子。

输入描述 Input Description
The first line will have the format N M, where N is the number of nodes in Budapest and M is the number of edges. The nodes are 1,2,…,N; node 1 represents s; node N represents t. Then there are M lines of the form A B L, indicating a one-way street from A to B of length L. You can assume that A != B on these lines, and that the ordered pairs (A,B) are distinct.

第一行包含两个整数N和M,N代表布达佩斯的节点个数,M代表边的个数。节点编号从1到N。1代表出发点s,N代表终点t。接下来的M行每行三个整数A B L,代表有一条从A到B的长度为L的单向同路。你可以认为A不等于B,也不会有重复的(A,B)对。

输出描述 Output Description
Output the length of a strictly-second-shortest route from s to t. If there are less than two possible lengths for routes from s to t, output −1.

输出从s到t的严格次短路的长度。如果从s到t的路少于2条,输出-1。

样例输入 Sample Input
样例输入1:

4 6

1 2 5

1 3 5

2 3 1

2 4 5

3 4 5

1 4 13

样例输入2:

2 2

1 2 1

2 1 1

样例输出 Sample Output
样例输出1:

11

样例输出2:

3

数据范围及提示 Data Size & Hint
对于样例1:

There are two shortest routes of length 10 (1 → 2 → 4,1 → 3 → 4) and
the strictly-second- shortest route is 1 → 2 → 3 → 4 with length 11.

对于样例2:

The shortest route is 1 → 2 of length 1, and the strictly-second route
is 1 → 2 → 1 → 2 of length 3.

跟最短路做法差不多,另开一个数组记录次短路即可。

如果 from 的最短路不能更新 to 的最短路,但是可以更新 to 的次短路,那就让它更新去吧,我们也不用再储存 以前的 to 的次短路值 , 因为那就是第 3 短路了 没用。

如果 from 的最短路可以更新 to 的最短路,那我们让 to 的次短路 等于 更新之前 to 的最短路,就是把 to 的最短路移到次短路上,因为我们有更短了路了。

如果 from 的次短路可以更新 to 的次短路 那也让它更新去吧。

而且初始值要尽量大,因为有的数据会比21474836还大
悲剧啊……

#include<iostream>
#include<cstdio>
#include<queue>
#include<algorithm>
#include<cstring>
using namespace std;
const int MAXN = 2000000;
struct Edge
{
    int from, to, cost;
}es[MAXN];
int first[MAXN], nxt[MAXN] , tot = 1;
long long zd[MAXN], cd[MAXN];       //怒开 long long
bool used[MAXN];
queue < int > q;

void build(int f, int t, int d)
{
    es[++tot].from = f;
    es[tot].to = t;
    es[tot].cost = d;
    nxt[tot] = first[f];
    first[f] = tot;
}

void spfa(int s)
{
    zd[s] = 0;
    q.push(s);
    used[s] = 1;
    while(!q.empty())
    {
        int u = q.front();
        q.pop();
        used[u] = 0;
        for(int i = first[u]; i != -1; i = nxt[i])
        {
            int v = es[i].to;
            if(zd[v] > zd[u] + es[i].cost)
            {
                cd[v] = zd[v];
                zd[v] = zd[u] + es[i].cost;
                if(!used[v])
                {
                    q.push(v);
                    used[v] = 1;
                }
            }
            else if(cd[v] > zd[u] + es[i].cost && zd[v] < zd[u] + es[i].cost)
            {
                cd[v] = zd[u] + es[i].cost;
                if(!used[v])
                {
                    q.push(v);
                    used[v] = 1;
                }
            }
            else if(cd[v] > cd[u] + es[i].cost )
            {
                cd[v] = cd[u] + es[i].cost;
                if(!used[v])
                {
                    q.push(v);
                    used[v] = 1;
                }
            }
        }
    }
}

int main()
{
    int n, m;
    scanf("%d%d", &n, &m);
    memset(first, -1, sizeof(first));
    for(int i = 1; i <= m; i++)
    {
        int s, e, c;
        scanf("%d%d%d", &s, &e, &c);
        build(s, e, c);
    }
    for(int i = 1; i <= n; i++)
    {
        zd[i] = 214748362222;
        cd[i] = 214748362222;
    }
    zd[1] = 0;
    spfa(1);
    if(cd[n] < 214748362222)
        printf("%lld", cd[n]);
    else
        printf("-1");
    return 0;
}

转载于:https://www.cnblogs.com/Loi-Vampire/p/6017066.html

**SPFA + 堆优化通常没有实际好处,反而可能更慢或出错**。下面我们详细解释为什么。 --- ### 一、SPFA 本身是什么? SPFA(Shortest Path Faster Algorithm)是 **Bellman-Ford 算法的队列优化版本**,核心思想是: - 只有当某个节点的最短距离被更新了,才用它去松弛其邻接点。 - 使用一个普通队列(FIFO)来维护待处理的节点。 ```python from collections import deque def spfa(n, graph, start): dist = [float('inf')] * n in_queue = [False] * n dist[start] = 0 in_queue[start] = True q = deque([start]) while q: u = q.popleft() in_queue[u] = False for v, w in graph[u]: if dist[u] + w < dist[v]: dist[v] = dist[u] + w if not in_queue[v]: q.append(v) in_queue[v] = True return dist ``` ✅ SPFA 的优点: - 能处理负权边 - 平均性能较好(尤其稀疏图) - 实现简单 --- ### 二、能不能加“堆优化”?比如用优先队列代替普通队列? 你可能会想:“Dijkstra 用堆优化更快,那 SPFA 也用堆试试?” 即:把 `deque` 换成 `heapq`,每次取出当前 `dist` 最小的节点来松弛 —— 这听起来像 Dijkstra! 但问题来了: > ❌ **一旦你用堆来取最小距离节点,你就不再是 SPFA,也不再是正确的负权最短路算法!** --- ### 三、为什么 SPFA 不适合堆优化? #### ✅ 正确性问题:堆优化版 SPFA 实际上退化为 Dijkstra - Dijkstra 使用堆的前提是:**边权非负** - 如果你在存在负权边的情况下使用堆,会出现: - 某个节点虽然当前 `dist` 小,但它之后可能通过负权边变得更小 - 但堆结构会优先处理“当前小”的点,并可能提前将其弹出,不再更新 - 导致无法正确传播后续的更优路径 → **结果错误** #### ⚠️ 示例说明: ``` A --1--> B --(-2)--> C \ / \--3------------>/ ``` 从 A 出发: - 初始:dist[A]=0, dist[B]=1, dist[C]=3 - 若使用堆,先处理 B(dist=1),然后更新 C:1 + (-2) = -1 → 更好 - 再处理 C(现在 dist[C]=-1) - 但如果图中有环或更多节点,堆可能已经忽略了某些本该再次更新的节点 但更大的问题是:**堆中元素一旦被取出,就不再重新考虑其更优值的可能性**(除非重新入堆),而频繁入堆会导致复杂度失控。 --- ### 四、性能分析:堆优化对 SPFA 没有收益 | 版本 | 数据结构 | 时间复杂度(平均) | 是否支持负权 | 稳定性 | |------|----------|---------------------|---------------|--------| | SPFA | 队列(deque) | O(kE),k 很小 | ✅ 支持 | 较好 | | SPFA + 堆 | 优先队列(heapq) | 接近 O(E log V) | ❌ 在负权下易错 | 差 | | Dijkstra + 堆 | 优先队列 | O((V+E) log V) | ❌ 不支持负权 | 稳定 | 👉 结论: - 堆优化并不能提升 SPFA 的效率 - 反而破坏了 SPFA “允许反复更新”的机制 - 在负权图中可能导致遗漏更优路径 --- ### 五、有没有“带堆的正确算法”能处理负权? 目前主流算法中: - **没有基于堆的通用负权最短路算法** - Johnson 算法会在预处理时重赋权,然后用 Dijkstra 多次求解,其中用了堆,但前提是先消除负权 - 所以堆的作用是在**非负权子问题**中加速 --- ### 六、总结 ❌ **不要给 SPFA 加堆优化!** - ✅ SPFA 应该用普通队列(或双端队列) - ❌ 堆优化不仅不能提高性能,还可能导致错误结果 - 🚫 它违背了 SPFA 的设计初衷:动态更新和多次入队 - 🔁 SPFA 的优势就在于灵活地处理负权边,而堆会限制这种灵活性 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值