全源最短路(Johnson)

Johnson 全源最短路详解(小白向) Part -1 前置算法 你需了解dijkstra算法与bellman_ford算法。 如果你想了解dijkstra,你可以去看程序员小灰的文章,C++代码我会在文章末尾给出(dijkstra,floyd,bellmen_ford和主角johnson) 如果你想了解bellman_ford,可以参考《算法笔记》中的bellman_ford讲解,或者百度一下。 Part 1 起因 nlogn的堆优化dijkstra跑n次也要比n^3的floyd快。用dijkstra跑全源最短路应该不是一个坏主意。于是我询问了 阅读详情

1.前言

当大家学会了floyd、Dijkstra和SPFA时,可能会好奇,Dijkstra和SPFA都是单源最短路,floyd是多源最短路,但是它的时间复杂度是O(n^3),那有没有时间复杂度更低,而且能处理负权的多源最短路呢?

2.引入

如果我们不能使用floyd,那么我们的多源最短路应该怎么求呢?肯定是跑n次SPFA,但是很明显,时间复杂度为O(n^2m),还不如floyd的时间复杂度。那如果每条边的权值肯定不是负数呢,跑n次堆优化的Dijkstra,时间复杂度是O(n((n+m) log m)),这样一来时间复杂度得到了优化,但是如何把每条边都变成正数呢?

3.Johnson思路

先建一个超级源点--0,然后向每一个节点建一条边,每条边的边权为零。

定义h(i),代表从超级源点到i的最短路

根据上图所示,我们可以算出来h(i):

12345
0-50-30

拿h(2)举个例子:(下图中红色是最短路径)

这里我们就跑一次SPFA,初始化出h[i]

然后,w'(u,v) = w(u,v)+h[u]-h[v]

更新其他边的长度,这样一来所有边都变成了正数

证明:

w(u,v)+h[u]-h[v]一定不是负数

 h[v]通过这条路进行更新 -> h[v]=h[u]+w(u,v) 

如果没有通过这条路进行更新的话, 那么代表h[v]肯定是短于h[u]+w(u,v)才不从u更新的

即:h[v] <= h[u]+w(u,v)

也就是0 <= h[u]+w(u,v)-h[v]

既然都大于等于零了就一定不是负数

现在所有值都变成正数了,用Dijkstra跑N遍最短路,

最后的输出dis’[u][v]-h[u]+h[v](因为我们要把它的边权改过了,我们要给它复原成原来的最短路)

证明二:原来的图的最短路dis与新图dis‘的关系是dis[u][v]=dis’[u][v]-h[u]+h[v]

假设最短路路径是u->a->b->c->...->z->v

那路权是w'(u,a)+w'(a,b)+w'(b,c)+...+w'(z,v)

展开得到

w(u,a)+h[u]-h[a]+w(a,b)+h[a]-h[b]+w(b,c)+h[b]-h[c]+...+w(z,v)+h[z]-h[v]

发现可以抵消得到

w(u,a)+w(a,b)+w(b,c)+...+w(z,v)+h[u]-h[v]

w(u,a)+w(a,b)+w(b,c)+...+w(z,v)是原来最短路的值,即dis[u][v]

替换可得

dis[u][v] +h[u]-h[v] = dis’[u][v]

也就是

dis[u][v]=dis’[u][v]-h[u]+h[v]

4.Johnson实现

P5905 【模板】全源最短路(Johnson)

题目中还有有负环的情况,这种情况可以通过SPFA判断,通过进队次数,判断是否有负环,一个点最多进队过n-1次(其他所有的元素都维护了一下它)这里实现还是挺简单的

接下来就是SPFA的代码:

int SPFA(){
    int v[3004] = {0};
    for (int i = 1; i <= n; i++){
        h[i] = 1e18;//初始化
    }
    h[0] = 0;
    v[0] = 1;
    int cnt[3004] = {0};//每一个点的入队次数
    cnt[1] = 1;
    queue <int> q;
    q.push(0);
    while (!q.empty()){
        int x = q.front();
        q.pop();
        v[x] = 0;
        for (int i = 0; i < a[x].size(); i++){
            int y = a[x][i].y;
            if (h[x]+a[x][i].w < h[y]){
                h[y] = h[x]+a[x][i].w;
                if (!v[y]){
                    cnt[y]++;
                    if (cnt[y] > n-1){//超过n-1即为负环
                        return 1;
                    }
                    v[y] = 1;
                q.push(y);
                }
                
            }
        }
    }
    return 0;
}

以及我前面定义的一些变量

struct node{
    int y,w;//增加了一个边权(w)
};
vector <node> a[30005];
int h[3004];//这里我们就跑一次SPFA,初始化出h[i]
int dis[3004];
int vis[3004];
int n,m;

然后是Dijkstra

void dij(int st){
        priority_queue<pair<int,int>,vector<pair<int,int>>,greater<pair<int,int>>> pq;
    for (int i = 1; i <= n; i++){
        dis[i] = 1e18;//初始化
    }
    memset(vis,0,sizeof(vis));
    dis[st] = 0;
    pq.push({0,st});//{权,序号}
    while (!pq.empty()){
        int jin = pq.top().second;
        pq.pop();
        if (vis[jin] == 1){
            continue;
        }
        for (int j = 0; j < a[jin].size(); j++){
            int v = a[jin][j].y;
            int w = a[jin][j].w;
            if (dis[jin]+w < dis[v]){
                dis[v] = dis[jin]+w;
                pq.push({dis[v],v});
            }
        }
        vis[jin] =1;
    }
}

主函数部分

signed main(){
    cin >> n >> m;
    for (int i = 1; i <= m; i++){
        int u,v,w;
        cin >> u >> v >> w;
        a[u].push_back({v,w});
    }
    for (int i = 1; i <= n; i++){
        a[0].push_back({i,0});//我们建一个超级源点,向每一个点建一条边,而这条边的权值为零,
    }
    if (SPFA()){
        cout << -1;
        return 0;
    }
    for (int x = 1; x <= n; x++){
        for (int i = 0; i < a[x].size(); i++){
            int y = a[x][i].y;
            a[x][i].w += h[x]-h[y];//然后,w'(u,v) = w(u,v)+h[u]-h[v]
        }
    }
//现在所有值都变成正数了用迪杰斯特拉跑N遍最短路,
    for (int i = 1; i <= n; i++){
        dij(i);
    
        long long cnt = 0;
        for (int j = 1; j <= n; j++){
            if (dis[j] >= 1e17){//不可能满足
                cnt += 1e9 * j;
            }
            else{
            cnt += 1LL*(dis[j] - h[i] + h[j])*j;//最后的输出dis’[u][v]-h[u]+h[v]
                
            }
        }
        cout << cnt << "\n";
    }
    return 0;
}

整体代码:

#include <bits/stdc++.h>
using namespace std;
#define int long long
struct node{
    int y,w;
};
vector <node> a[30005];
int h[3004];//这里我们就跑一次SPFA,初始化出h[i]
int dis[3004];
int vis[3004];
    int n,m;
int SPFA(){
    int v[3004] = {0};
    for (int i = 1; i <= n; i++){
        h[i] = 1e18;//初始化
    }
    h[0] = 0;
    v[0] = 1;
    int cnt[3004] = {0};//每一个点的入队次数
    cnt[1] = 1;
    queue <int> q;
    q.push(0);
    while (!q.empty()){
        int x = q.front();
        q.pop();
        v[x] = 0;
        for (int i = 0; i < a[x].size(); i++){
            int y = a[x][i].y;
            if (h[x]+a[x][i].w < h[y]){
                h[y] = h[x]+a[x][i].w;
                if (!v[y]){
                    cnt[y]++;
                    if (cnt[y] > n-1){//超过n-1即为负环
                        return 1;
                    }
                    v[y] = 1;
                q.push(y);
                }
                
            }
        }
    }
    return 0;
}
void dij(int st){
        priority_queue<pair<int,int>,vector<pair<int,int>>,greater<pair<int,int>>> pq;
    for (int i = 1; i <= n; i++){
        dis[i] = 1e18;//初始化
    }
    memset(vis,0,sizeof(vis));
    dis[st] = 0;
    pq.push({0,st});//{权,序号}
    while (!pq.empty()){
        int jin = pq.top().second;
        pq.pop();
        if (vis[jin] == 1){
            continue;
        }
        for (int j = 0; j < a[jin].size(); j++){
            int v = a[jin][j].y;
            int w = a[jin][j].w;
            if (dis[jin]+w < dis[v]){
                dis[v] = dis[jin]+w;
                pq.push({dis[v],v});
            }
        }
        vis[jin] =1;
    }
}
signed main(){
    cin >> n >> m;
    for (int i = 1; i <= m; i++){
        int u,v,w;
        cin >> u >> v >> w;
        a[u].push_back({v,w});
    }
    for (int i = 1; i <= n; i++){
        a[0].push_back({i,0});//我们建一个超级原点,向每一个点建一条边,而这条边的权值为零,
    }
    if (SPFA()){
        cout << -1;
        return 0;
    }
    for (int x = 1; x <= n; x++){
        for (int i = 0; i < a[x].size(); i++){
            int y = a[x][i].y;
            a[x][i].w += h[x]-h[y];//然后,w'(u,v) = w(u,v)+h[u]-h[v]
        }
    }
//现在所有值都变成正数了用迪杰斯特拉跑N遍最短路,
    for (int i = 1; i <= n; i++){
        dij(i);
    //最后的输出dis’[u][v]-h[u]+h[v]
        long long cnt = 0;
        for (int j = 1; j <= n; j++){
            if (dis[j] >= 1e17){
                cnt += 1e9 * j;
            }
            else{
            cnt += 1LL*(dis[j] - h[i] + h[j])*j;
                
            }
        }
        cout << cnt << "\n";
    }
    return 0;
}

从0开始详解 Johnson 全源最短路(P5905 【模板】Johnson 全源最短路) 问题引入:(【模板】Johnson 全源最短路 - 洛谷) ps:(如果不想 情景带入 请直接转跳到Johnson算法详解) 目录 问题引入:(【模板】Johnson 全源最短路 - 洛谷) 题目描述 输出格式 说明/提示 【数据范围】 常用最短路时间复杂度: Johnson 全源最短路 详解 第一个问题:如何可以把负权都转变成正权 题目描述 给定一个包含 n 个结点和 m 条带权边的有向图,求所有点对间的最短路径长度,一条路径的长度定义为这条路径上所有边的权值和。 .. 阅读详情

相关推荐

题解 [LuoguP5905]【模板】Johnson 全源最短路

Johnson全源最短路

im_zyINF的博客 236

全源最短路Johnson算法

给定一个包含 nnn 个结点和 mmm 条带权边的有向图,求所有点对间的最短路径长度,一条路径的长度定义为这条路径上所有边的权值和。注意:边权可能为负,且图中可能存在重边和自环;部分数据卡 nnn 轮 SPFA 算法。第 111 行:222 个整数 n,mn,mn,m,表示给定有向图的结点数量和有向边数量。接下来 mmm 行:每行 333 个整数 u,v,wu,v,wu,v,w,表示有一条权值为 www 的有向边从编号为 uuu 的结点连向编号为 vvv 的结点。若图中存在负环,输出仅一行 −1-1−1。若

重生之我是cxk的博客 601

洛谷 P5905 【模板】Johnson 全源最短路(spfa判负环,Johnson算法)

Johnson 全源最短路(spfa判负环,Johnson算法)

qq_38232157的博客 406

洛谷 P5905 【模板】Johnson 全源最短路(Johnson算法)

https://www.luogu.com.cn/problem/P5905 思路 先跑spfa找到“势能”以及是否有闭环,然后每个点Dijkstra找最短路。 代码 #include <bits/stdc++.h> using namespace std; const int N = 5005; const int M = 60006; const int INF = 1e9; struct edge { long long v, w, next; } e[M]; struct nod.

We have to be very hard to reach the top. 462

双机调度问题Johnson算法C++实现

双机流水作业调度:总共有n个作业,作业i分为两个内容,需要按顺序先后在机器A和机器B上完成,分别需要时间ai,bi来完成,一台机器只能同时进行一项作业,问完成所有作业所需的最小时间。多机流水作业调度:一个作业需要在大于两台机器上先后完成,是NP-hard问题。问题就是求最佳作业序列。设前i项作业所需的时间为Ci,可以得出以下式子。

程序猿老樊的博客 641

8.3.2 蓝桥杯图论之Johnson全源最短路

Johnson算法结合了Bellman-Ford算法和Dijkstra算法的优点,能够在带有负权边的图中计算全源最短路径。其核心思想是先使用Bellman-Ford算法重新赋权,以消除负权边的影响,然后对每个顶点运行Dijkstra算法计算最短路径。Johnson算法通过巧妙地结合Bellman-Ford算法和Dijkstra算法,提供了一种在包含负权边的图中计算全源最短路径的有效方法。通过本文的介绍,您应该能够理解Johnson算法的原理和步骤,并知道如何在C++中实现它。

tang7mj的博客 696

Luogu P5905 【模板】全源最短路(Johnson) 题解 全源最短路 Johnson算法

其思想就是将一个含有负权边的图变成无负权边的图之后,跑。首先解释修改边权后一定不存在负边,在最短路中如果存在。,因此只需要在最后减去多加的一部分即可获得最终的最短路。这也就表明修改后的每条边权值均为正。给定一张有向图,求出任意两点之间的最短路。上述算法的时间复杂度为。之间的最短路依次经过。

qq_45523675的博客 666

在 Zemax 非序列模式下建立干涉仪模型

Zemax 利用其相干检测功能,将到达探测器的射线的能量相干地组合起来,同时考虑每条检测到的射线的幅度和相位。从对这些干涉条纹的定量评估来看,光学干涉仪具有广泛的应用,包括在天文学中用于测量天体的大小,在计量学中用于精确测量距离,在显微镜中用于提高分辨率,以及在物理和工程的各个领域中用于检测条件的微小变化。在光学干涉仪中,使用的是光波。非相干和相干相加的总功率是守恒的,但相干光束的峰值辐照度应该是非相干光束总和的 2 倍,这是理所当然的。请注意,条纹的缺失是显而易见的,因为不再从相干的角度观察探测器。

jkh920184196的博客 2382

P5905 【模板】Johnson 全源最短路

这玩意今天才知道,但感觉用处不大啊。。。 P5905 【模板】Johnson 全源最短路 #include <bits/stdc++.h> using namespace std; int n,m; struct node1 { int d,w; }; vector< node1 > e[30010]; struct node2 { int v,d; bool operator < (const node2 &y)const {

beneath_haruhi的博客 191

在 CentOS7 下安装 Cadence IC验证平台 INCISIVE152

前言 本文所使用的软件 虚拟机软件:VMware Workstation 15 PRO 如何安装? 操作系统:CentOS-7, CentOS-7-x86_64-DVD-1810.iso 如何安装? 简介 INCISIVE Incisive是nc_verilog, nc_sim, nc_lauch,以及ABV,TBV的集合,用于仿真和验证。 InstallScape InstallSc...

沈醉不知的博客 2万+

手把手教你用内核远程call实现64位进程注入(附完整ShellCode示例)

本文深入探讨了在Windows 64位环境下,通过内核态直接操作线程TrapFrame结构实现进程注入的高级技术。文章提供了从驱动框架设计、用户态-内核态通信,到定位修改TrapFrame、编写Shellcode及执行环境恢复的完整实战指南,并附有详细的代码示例,为安全研究人员和开发者提供了绕过用户态检测、实现底层远程代码执行的新思路。

weixin_28396629的博客 346

全源最短路封装模板(APSP,Floyd求最小环,Floyd求最短路,Johnson算法)

Floyd,Johnson,最小环,全源最短路

surtr1的博客 1150

deepfacelab下载安装及训练(小白篇),以及导出SAEHD为dfm文件,解决broken pipe问题

其中如果用的是RTX30系列显卡的话,需要把WIN10升级到20H4或更新版本,然后打开硬件加速,不然会出现训练时一直卡在第一个迭代不动了,我的服务器版本为22H2,符合。支持WIN10,win11,WIN7,LINUX,不支持mac(因为mac不支持NVIDIA显卡)

caoyongsheng的博客 3916

Johnson 全源最短路

“ 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

Ctrl AC的博客 422

现代 AI Agent 编排技术全景(1)—— 分类体系与四大技术维度

本文介绍基于 LLM 的现代 Agent 分类体系与四大技术维度,形成能够刻画不同 Agent 产品架构特征的分类法

佚失的诗篇 238

【点云概述】什么是点云、来源、种类、特点、处理?

【点云概述】什么是点云、来源、种类、特点、处理?点云(point cloud)是空间中点的数据集,可以表示三维形状或对象,通常由三维扫描仪获取。点云中每个点的位置都由一组笛卡尔坐标 ( X , Y , Z ) (X, Y, Z) (X,Y,Z)描述,有些可能含有色彩信息 ( R , G , B ) (R, G, B) (R,G,B)或物体反射面强度(Intensity)信。

智慧空间实验室 9822

P5905 【模板】Johnson 全源最短路(存在负边权的全源最短路)

题目传送门 题意: 给你一张图,可能存在负的边,如果存在负环则输出-1,否则按照题目要求输出 求和公式不会打 。 对于这个,判断是否存在负环跑spfa即可。如果不存在负环,我们可以用n遍spfa去处理这个问题,spfa的时间复杂度是O(kE),k是常数,最坏可以达到O(VE),V即顶点数,跑n遍那就是O(V2E),那有没有更加理想的算法呢?有!Johnson算法。 Johnson算法 在一个带负边权但无负环的图中,我们建立一个虚点,并且从这个虚点出发到每一个点都建立一条长度为0的有向边,以这个虚点为源点,

Joker_He的博客 393

飞腾UEFI配置GPIO—飞腾FT2000/4 (D2000/8) GPIO引脚配置及使用

讲解了在飞腾UEFI代码中如何添加GPIO配置相关代码,并以GPIO1_B0—B7为例,将这8个引脚配置为GPIO工作模式并默认输出低电平

qq_30135687的博客 5350

20250812 全源最短路总结

如上图,假设我们要求1到3的最短路,先从1走到2,此时边权和为3+h[1]-h[2],接着再从2走到3,此时总边权为3+h[1]-h[2]-2+h[2]-h[3],结合起来就是h[1]-h[3]+1。h是从超级源点出发到每个点的最短距离,所以对于任意一条边(u, v, w),一定有hv <= hu + w,上式移项得w + hu - hv >= 0,即新图中的边权非负。我们可以把h想成一种势能,因为势能只跟起点和终点有关,所以dijkstra求出答案后只要减去h[u]加上h[v],就能得出最终结果。

scx20131004的博客 1115

4.2 多智能体系统的严格隔离机制:防止上下文污染与隐私数据脱敏实战

本文探讨了多智能体系统中上下文信息共享带来的安全风险与解决方案。研究发现,随着Agent数量增加,上下文信息泄露概率呈指数级上升,可能导致跨Agent错误传播、隐私泄露等问题。文章提出分层隔离架构,将上下文划分为全局共享和本地私有两部分,并通过命名空间隔离、递归压缩和隐私过滤技术确保数据安全。技术实现上,建议为每个子Agent创建独立状态空间,采用原子化更新和读写锁控制避免状态竞争。隐私保护方面,结合正则匹配和命名实体识别技术进行敏感数据过滤,确保符合GDPR等法规要求。这些措施共同构建了可靠、安全且合规的

公众号:AI前沿侦探 228

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

P5905 【模板】全源最短路(Johnson) 题解

P5905 【模板】全源最短路(Johnson) 题解

yaosichengalpha的博客 1087

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

P5905 【模板】Johnson 全源最短路 题解

原题链接 前置知识: dijkstra\text{dijkstra}dijkstra 模板 简要题意: 求任意两点的最短路。图中可能有 负环,负权,重边,自环 等现象。 显然我们先建图。 算法一 对于 20%20\%20% 的数据,1≤n≤1001\leq n \leq 1001≤n≤100,不存在负环(可用于验证 Floyd\text{Floyd}Floyd 正确性) 嗯,出题人都告诉你用 ...

bifanwen的博客 586

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

上一篇: P1776 宝物筛选
下一篇: 矩阵乘法-简记
B757-300
博客等级 码龄4年 259粉丝 52原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值