Johnson全源最短路

板子题:https://www.luogu.com.cn/problem/P5905

个人认为是最难的一个最短路算法了,因为实现中用到了很多其他的最短路

数据:

负权边:支持

判断负环:不支持

复杂度:NMlogM

(实际上是O(N·dijkstra),这里采用堆优化dijkstra)

前置知识

bellmanfordspfa(可选),dijkstra,链式前向星

没错要学这个最短路其他的都得会。

基本思路

通过复杂度可以看出,这个算法与 ndijkstra 一样快。

那为什么不用 ndijkstra 呢?有负权。

那怎么解决呢?重新标权值,且保证正确性。

加一个新点0,将0与每个点之间连一条长度为0的边。

然后求0到每个点的最短路,记0到 i 的最短路为hi

更新每个边edge_len(u,v)的权值为edge_len(u,v)+huhv

三角形两边之和大于第三边,所以edge_len(u,v)+huhv

(SUV有可能退化成线段SV,所以是)

所以这个图上每个边就都是非负的了,就可以跑 ndijkstra 啦。

然后把每个源点的最短路hu+hv还原回去,就是正确答案了。

正确性证明

设起始点为 s,终点为 t

最短路:sp0p1p2...pkt

原图中的最短路长度:w(s,p0)+w(p0,p1)+w(p1,p2)+...+w(pk,t)

新图中的最短路长度:w(s,p0)+hshp0+w(p0,p1)+hp0hp1+w(p1,p2)+hp1hp2+...+w(pk,t)+hpkht

你会发现,带 p 的项都消了!

则新图中最短路长度为:w(s,p0)+w(p0,p1)+w(p1,p2)+...+w(pk,t)+hsht

又因为两点间势能的差为定值,即hsht为定值,

所以新图中的最短路与原图中的最短路对应。

实现思路

求0到每个点的最短路,因为图中有负权,可以用bellmanfordspfa

但这样连出来的图一定是个菊花图,选择这两种的效率没什么区别。

而且这不影响最后的时间复杂度,时间复杂度由ndijkstra决定。

然后枚举点u,再枚举所有能到达的点v,更新u到v的权值w。

最后跑ndijkstra即可。

另外,对于这道题,要判断负环。

可以在求0到每个点的最短路时顺便用bellmanford或者spfa判断负环

代码

#include <iostream>
#include <queue>
#include <utility>
#include <functional>
#include <cstring>
using namespace std;
struct Edge
{
    int v, w, nxt;
}edge[9001];
int n, m, u, v, w, head[3001], cnt;long long dis[3001], h[3001], ans;
void add(int u, int v, int w) //加边
{
    ++cnt;
    edge[cnt].v = v;
    edge[cnt].w = w;
    edge[cnt].nxt = head[u];
    head[u] = cnt;
}
void dijkstra(int s) //正常的dijkstra
{
    priority_queue<pair<int, int>, vector<pair<int, int> >, greater<pair<int, int> > > q;bool vis[3001];memset(vis, 0, sizeof vis);
    for(int i = 1;i <= n;++i) dis[i] = 1e9;
    dis[s] = 0;q.push(make_pair(0, s));
    while(!q.empty())
    {
        int u = q.top().second;q.pop();
        if(vis[u]) continue;
        vis[u] = 1;
        for(int i = head[u];i;i = edge[i].nxt)
            if(dis[edge[i].v] > dis[u] + edge[i].w)
            {
                dis[edge[i].v] = dis[u] + edge[i].w;
                q.push(make_pair(dis[edge[i].v], edge[i].v));
            }
    }
}
int main()
{
    cin >> n >> m;
    for(int i = 0;i < m;++i)
        cin >> u >> v >> w, add(u, v, w);
    for(int i = 1;i <= n;++i)
        add(0, i, 0), h[i] = 1e9; //加从0到每个点的边,权值为0
    h[0] = 0;
    for(int i = 1;i < n;++i) //开始bellman-ford
        for(int u = 0;u <= n;++u)
            for(int j = head[u];j;j = edge[j].nxt)
                h[edge[j].v] = min(h[edge[j].v], h[u] + edge[j].w);
    for(int u = 1;u <= n;++u) //判负环
        for(int i = head[u];i;i = edge[i].nxt)
            if(h[edge[i].v] > h[u] + edge[i].w)
            {
                cout << -1;
                return 0;
            }
    for(int u = 1;u <= n;++u)
        for(int i = head[u];i;i = edge[i].nxt)
            edge[i].w = edge[i].w + h[u] - h[edge[i].v]; //更新权值
    for(int u = 1;u <= n;++u)
    {
        dijkstra(u);ans = 0;
        for(int v = 1;v <= n;++v)
            if(dis[v] == 1e9) ans += v * 1e9;
            else ans += v * (dis[v] - h[u] + h[v]); //还原权值
        cout << ans << endl;
    }
    return 0;
}
posted @   Jijidawang  阅读(4)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示