Johnson 全源最短路

这篇博客详细介绍了如何运用Johnson算法来找到加权有向图中的最短路径。首先,通过添加一个源节点s并连接所有其他节点,使得新图中所有路径的起点统一。然后,利用Bellman-Ford或SPFA算法更新s到所有点的最短距离。接着,调整原图的边权重,并移除s节点。最后,从每个节点出发重新计算最短路径。代码示例展示了完整的Johnson算法实现过程,适用于解决大规模图的最短路径问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

“ Ctrl AC!一起 AC!”

步骤:

· 对于原图,新增一个结点s,使该点与所有点之间建立一条边,权重为零。

· 对新图使用bellman或spfa求得新点s到其他点的最短距离,h[0]....h[V-1]

· 对原图进行边权更新,w`(u,v)=w(u,v)+(h[u]-h[v])

· 移除新的顶点s,以每个顶点为起点求出最短路

代码:

配套题目:Johnson

#include <cstring>
#include <iostream>
#include <queue>
#define INF 1e9
using namespace std;
struct edge {
    int v, w, next;
} e[10005];
struct node {
    int dis, id;
    bool operator<(const node& a) const { return dis > a.dis; }
    node(int d, int x) { dis = d, id = x; }
};
int head[5005], vis[5005], t[5005];
int cnt, n, m;
long long h[5005], dis[5005];
void addedge(int u, int v, int w) {
    e[++cnt].v = v;
    e[cnt].w = w;
    e[cnt].next = head[u];
    head[u] = cnt;
}
bool spfa(int s) {
    queue<int> q;
    memset(h, 63, sizeof(h));
    h[s] = 0, vis[s] = 1;
    q.push(s);
    while (!q.empty()) {
        int u = q.front();
        q.pop();
        vis[u] = 0;
        for (int i = head[u]; i; i = e[i].next) {
            int v = e[i].v;
            if (h[v] > h[u] + e[i].w) {
                h[v] = h[u] + e[i].w;
                if (!vis[v]) {
                    vis[v] = 1;
                    q.push(v);
                    t[v]++;
                    if (t[v] == n + 1) return false;
                }
            }
        }
    }
    return true;
}
void dijkstra(int s) {
    priority_queue<node> q;
    for (int i = 1; i <= n; i++) dis[i] = INF;
    memset(vis, 0, sizeof(vis));
    dis[s] = 0;
    q.push(node(0,s));
    while (!q.empty()) {
        int u = q.top().id;
        q.pop();
        if (vis[u]) continue;
        vis[u] = 1;
        for (int i = head[u]; i; i = e[i].next) {
            int v = e[i].v;
            if (dis[v] > dis[u] + e[i].w) {
                dis[v] = dis[u] + e[i].w;
                if (!vis[v]) q.push(node(dis[v],v));
            }
        }
    }
    return;
}
int main() {
    cin >> n >> m;
    for (int i = 1; i <= m; i++) {
        int u, v, w;
        cin >> u >> v >> w;
        addedge(u, v, w);
    }
    for (int i = 1; i <= n; i++) addedge(0, i, 0);
    if (!spfa(0)) {
        cout << -1 << endl;
        return 0;
    }
    for (int u = 1; u <= n; u++)
        for (int i = head[u]; i; i = e[i].next) e[i].w += h[u] - h[e[i].v];
    for (int i = 1; i <= n; i++) {
        dijkstra(i);
        long long ans = 0;
        for (int j = 1; j <= n; j++) {
            if (dis[j] == INF)
                ans += j * INF;
            else
                ans += j * (dis[j] + h[j] - h[i]);
        }
        cout << ans << endl;
    }
    return 0;
}

感谢阅读!!!

“ Ctrl AC!一起 AC!”

# P5905 【模板】全源短路Johnson) ## 题目描述 给定一个包含 $n$ 个结点和 $m$ 条带权边的有向图,求所有点对间的短路径长度,一条路径的长度定义为这条路径上所有边的权值和。 注意: 1. 边权**可能**为负,且图中**可能**存在重边和自环; 2. 部分数据卡 $n$ 轮 SPFA 算法。 ## 输入格式 第 $1$ 行:$2$ 个整数 $n,m$,表示给定有向图的结点数量和有向边数量。 接下来 $m$ 行:每行 $3$ 个整数 $u,v,w$,表示有一条权值为 $w$ 的有向边从编号为 $u$ 的结点连向编号为 $v$ 的结点。 ## 输出格式 若图中存在负环,输出仅一行 $-1$。 若图中不存在负环: 输出 $n$ 行:令 $dis_{i,j}$ 为从 $i$ 到 $j$ 的短路,在第 $i$ 行输出 $\sum\limits_{j=1}^n j\times dis_{i,j}$,注意这个结果可能超过 int 存储范围。 如果不存在从 $i$ 到 $j$ 的路径,则 $dis_{i,j}=10^9$;如果 $i=j$,则 $dis_{i,j}=0$。 ## 输入输出样例 #1 ### 输入 #1 ``` 5 7 1 2 4 1 4 10 2 3 7 4 5 3 4 2 -2 3 4 -3 5 3 4 ``` ### 输出 #1 ``` 128 1000000072 999999978 1000000026 1000000014 ``` ## 输入输出样例 #2 ### 输入 #2 ``` 5 5 1 2 4 3 4 9 3 4 -3 4 5 3 5 3 -2 ``` ### 输出 #2 ``` -1 ``` ## 说明/提示 【样例解释】 左图为样例 $1$ 给出的有向图,短路构成的答案矩阵为: ``` 0 4 11 8 11 1000000000 0 7 4 7 1000000000 -5 0 -3 0 1000000000 -2 5 0 3 1000000000 -1 4 1 0 ``` 右图为样例 $2$ 给出的有向图,红色标注的边构成了负环,注意给出的图不一定连通。 ![](https://cdn.luogu.com.cn/upload/image_hosting/7lb35u4u.png) 【数据范围】 对于 $100\%$ 的数据,$1\leq n\leq 3\times 10^3,\ \ 1\leq m\leq 6\times 10^3,\ \ 1\leq u,v\leq n,\ \ -3\times 10^5\leq w\leq 3\times 10^5$。 对于 $20\%$ 的数据,$1\leq n\leq 100$,不存在负环(可用于验证 Floyd 正确性) 对于另外 $20\%$ 的数据,$w\ge 0$(可用于验证 Dijkstra 正确性) upd. 添加一组 Hack 数据:针对 SPFA 的 SLF 优化
最新发布
07-18
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ctrl AC

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值