SPFA 最短路CSP-S

目录

💡 核心思想

SPFA(Shortest Path Faster Algorithm)是 Bellman-Ford 的队列优化版本。它维护一个队列,只对"上次松弛成功的节点"的邻居进行松弛。可以处理负权边,也能检测负环。但最坏时间复杂度为 $O(VE)$。

SPFA 在实际中通常很快,但理论上可以被特殊数据卡到 $O(VE)$。NOI2018 中曾有一道题专门卡 SPFA,从此"SPFA 已死"成为名言。但在 CSP 竞赛中,如果图有负权边或需要判负环,SPFA 仍然是首选。

🎯 直觉理解

SPFA 就像"有消息才传播"。Dijkstra 是所有节点都关注,SPFA 是只有被更新的节点才通知邻居。如果一条消息在圈里传播了 $n$ 次还没停,说明有负环(消息越传越"好",永远不会停)。

📝 算法流程

  1. 初始化 dist[s]=0,其余 INF,s 入队
  2. 队首出队 u,对所有出边松弛
  3. 松弛成功且目标不在队中则入队
  4. 判负环:某节点入队次数超过 n 则有负环

$$dist[v] = \min(dist[v], dist[u] + w(u,v))$$

📊 复杂度分析

指标复杂度
时间平均 $O(kE)$(k 很小),最坏 $O(VE)$
空间$O(V+E)$

💻 参考实现(C++)

C++ (C++17)
#include <bits/stdc++.h>
using namespace std;
int main() {
    int n, m, s; cin >> n >> m >> s;
    vector<vector<pair<int,long long>>> adj(n+1);
    for (int i = 0; i < m; i++) {
        int u, v; long long w; cin >> u >> v >> w;
        adj[u].push_back({v, w});
    }
    const long long INF = 1e18;
    vector<long long> dist(n+1, INF);
    vector<bool> inq(n+1, false);
    vector<int> cnt(n+1, 0);
    queue<int> q;
    dist[s] = 0; q.push(s); inq[s] = true;
    while (!q.empty()) {
        int u = q.front(); q.pop(); inq[u] = false;
        for (auto [v, w] : adj[u]) {
            if (dist[u] + w < dist[v]) {
                dist[v] = dist[u] + w;
                if (!inq[v]) { q.push(v); inq[v] = true; if(++cnt[v]>n){cout<<"负环";return 0;} }
            }
        }
    }
    for (int i = 1; i <= n; i++) cout << dist[i] << " ";
    return 0;
}

⚠️ 常见坑点

被卡到 $O(VE)$ 时考虑换成 Dijkstra 或加优化

判负环时 cnt 数组要统计入队次数而非松弛次数

SLF 优化(新距离比队首小时放队首)可以加速

负环判断要在入队时做

📚 相关题目

题目来源难度备注
P3385 负环洛谷CSP-SSPFA判负环
P3371 单源最短路径弱化版洛谷CSP-JSPFA可过