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

首先考虑求全源最短路的几种方法:

  • Floyd:时间复杂度\(O(n^3)\),可以处理负权边,但不能处理负环,而且速度很慢。
  • Bellman-Ford:以每个点为源点做一次Bellman-Ford,时间复杂度\(O(n^2m)\),可以处理负权边,可以处理负环,但好像比Floyd还慢?
  • dijkstra:以每个点为源点做一次dijkstra,时间复杂度\(O(nmlogm)\),不能处理负权边,但比前面两个快多了。

好像……只有dijkstra还有希望?但负权边处理不了真是很棘手啊。

一种方法是让每条边都加上一个数\(x\)使得边权为正,但考虑下图:



\(1\)到\(2\)的最短路应为:\(1 -> 3 -> 4 -> 2\),长度为\(-1\)。如果我们把每条边的边权都加上\(5\):



此时的最短路是:\(1 -> 5 -> 2\),就不是实际的最短路了,所以这种方法行不通

注:经本人研究,应该是两条路径进过的边的数量不同而导致的

接下来,就该 Johnson 登场啦!Johnson 其实就是用另一种方法标记边权啦。

首先来看看实现方法:我们新建一个虚拟结点(不妨设他的编号为0),由他向其他的所有结点都连一条边权为\(0\)的边,然后求0号节点为源点的单源最短路,存到一个\(h\)数组中。然后,让每条边的权值\(w\)变为\(w+h_u-h_v\),这里\(u\)和\(v\)分别为这条边的起点和终点。然后再以每个点为源点做 dijkstra 就OK了。

Q:那这么说,Dijkstra 也可以求出负权图(无负环)的单源最短路径了?

A:没错。但是预处理要跑一遍 Bellman-Ford,还不如直接用 Bellman-Ford 呢。

如何证明这是正确的呢?

首先,从\(s\)到\(t\)的路径中随便取出一条:

\[s -> p_1 -> p_2 -> \cdots -> p_k -> t
\]

则这条路径的长度为:

\[(w_{s,p_1}+h_s-h_{p_1})+(w_{p_1,p_2}+h_{p_1}-h_{p_2})+\dots+(w_{p_k,t}+h_{p_k}-h_t)
\]

简化后得到:

\[w_{s,p_1}+w_{p_1,p_2}+\cdots+w_{p_k,t}+h_s-h_t
\]

可以发现,不管走哪条路径,最后都是\(+h_s-h_t\),而\(h_s\)和\(h_t\)又是不变的,所以最终得到的最短路径还是原来的最短路径。

到这里已经证明一半了,接下来要证明得到的边权非负,必须要无负权边才能使 dijkstra 跑出来的结果正确。根据三角形不等式(就是那个三角形里任意两条边的长度之和大于等于另一条边的长度),新图上的任意一条边\((u,v)\)上的两点满足:\(h_v \le w_{u,v}+h_u\),则新边的边权\(w_{u,v}+h_u-h_v \ge 0\)。所以新图的边权非负。

正确性证明就是这个亚子。

代码实现(注意处理精度问题,该开ll的时候开ll):

#include<cstdio>
#include<queue>
#define MAXN 5005
#define MAXM 10005
#define INF 1e9
using namespace std;
int n,m;
int vis[MAXN];
long long h[MAXN],dis[MAXN];
bool f[MAXN];
struct graph
{
int tot;
int hd[MAXN];
int nxt[MAXM],to[MAXM],dt[MAXM];
void add(int x,int y,int w)
{
tot++;
nxt[tot]=hd[x];
hd[x]=tot;
to[tot]=y;
dt[tot]=w;
return ;
}
}g;//链式前向星
bool SPFA(int s)//这里用了Bellman-Ford的队列优化
{
queue<int>q;
for(int i=1;i<=n;++i) h[i]=INF,f[i]=false;
h[s]=0;
f[s]=true;
q.push(s);
while(!q.empty())
{
int xx=q.front();
q.pop();
f[xx]=false;
for(int i=g.hd[xx];i;i=g.nxt[i])
if(h[g.to[i]]>h[xx]+g.dt[i])
{
h[g.to[i]]=h[xx]+g.dt[i];
if(!f[g.to[i]])
{
if(++vis[g.to[i]]>=n) return false;//注意在有重边的情况下要记录入队次数而不是松弛次数
f[g.to[i]]=true,q.push(g.to[i]);
}
}
}
return true;
}
void dijkstra(int s)
{
priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > > q;
for(int i=1;i<=n;i++) dis[i]=INF,f[i]=false;
q.push(make_pair(0,s));
dis[s]=0;
while(!q.empty())
{
int xx=q.top().second;
q.pop();
if(!f[xx])
{
f[xx]=true;
for(int i=g.hd[xx];i;i=g.nxt[i])
if(dis[g.to[i]]>dis[xx]+g.dt[i])
{
dis[g.to[i]]=dis[xx]+g.dt[i];
if(!f[g.to[i]])
q.push(make_pair(dis[g.to[i]],g.to[i]));
}
}
}
return ;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
g.add(u,v,w);
}
for(int i=1;i<=n;i++) g.add(0,i,0);//建虚拟节点0并且往其他的点都连一条边权为0的边
if(!SPFA(0))//求h的同时也判了负环
{
printf("-1");
return 0;
}
for(int u=1;u<=n;u++)
for(int i=g.hd[u];i;i=g.nxt[i])
g.dt[i]+=h[u]-h[g.to[i]];//求新边的边权
for(int i=1;i<=n;i++)
{
dijkstra(i);//以每个点为源点做一遍dijkstra
long long ans=0;
for(int j=1;j<=n;j++)//记录答案
if(dis[j]==INF) ans+=1ll*j*INF;
else ans+=1ll*j*(dis[j]+(h[j]-h[i]));
printf("%lld\n",ans);
}
return 0;
}

最后安利一发博客

Johnson全源最短路的更多相关文章

  1. 【学习笔记】 Johnson 全源最短路

    前置扯淡 一年多前学的最短路,当时就会了几个名词的拼写,啥也没想过 几个月之前,听说了"全源最短路"这个东西,当时也没说学一下,现在补一下(感觉实在是没啥用) 介绍 由于\(spf ...

  2. Johnson 全源最短路

    学这个是为了支持在带负权值的图上跑 Dijkstra. 为了这个我们要考虑把负的权值搞正. 那么先把我们先人已经得到的结论摆出来.我们考虑先用 SPFA 对着一个满足三角形不等式的图跑一次最短路,具体 ...

  3. Johnson 全源最短路径算法学习笔记

    Johnson 全源最短路径算法学习笔记 如果你希望得到带互动的极简文字体验,请点这里 我们来学习johnson Johnson 算法是一种在边加权有向图中找到所有顶点对之间最短路径的方法.它允许一些 ...

  4. Johnson 全源最短路径算法

    解决单源最短路径问题(Single Source Shortest Paths Problem)的算法包括: Dijkstra 单源最短路径算法:时间复杂度为 O(E + VlogV),要求权值非负: ...

  5. 模板C++ 03图论算法 2最短路之全源最短路(Floyd)

    3.2最短路之全源最短路(Floyd) 这个算法用于求所有点对的最短距离.比调用n次SPFA的优点在于代码简单,时间复杂度为O(n^3).[无法计算含有负环的图] 依次扫描每一点(k),并以该点作为中 ...

  6. Johnson算法:多源最短路算法

    Johnson算法 请不要轻易点击标题 一个可以在有负边的图上使用的多源最短路算法 时间复杂度\(O(n \cdot m \cdot log \ m+n \cdot m)\) 空间复杂度\(O(n+m ...

  7. Floyd-Warshall 全源最短路径算法

    Floyd-Warshall 算法采用动态规划方案来解决在一个有向图 G = (V, E) 上每对顶点间的最短路径问题,即全源最短路径问题(All-Pairs Shortest Paths Probl ...

  8. 【算法】单源最短路——Dijkstra

    对于固定起点的最短路算法,我们称之为单源最短路算法.单源最短路算法很多,最常见的就是dijkstra算法. dijkstra主要用的是一种贪心的思想,就是说如果i...s...t...j是最短路,那么 ...

  9. 图论:Floyd-多源最短路、无向图最小环

    在最短路问题中,如果我们面对的是稠密图(十分稠密的那种,比如说全连接图),计算多源最短路的时候,Floyd算法才能充分发挥它的优势,彻彻底底打败SPFA和Dijkstra 在别的最短路问题中都不推荐使 ...

随机推荐

  1. 一站式搞定Ubuntu共享环境配置

    1. 添加linux用户 安装的开发用的虚拟机,一般不直接使用root账户,会新建一个普通用户,然后在/etc/sudoers添加上sudo的权限即可. 使用如下命令: sudo adduser -- ...

  2. 解惑4:java是值传递还是引用传递

    一.概述 曾经纠结了很久java的参数传递方式是什么样的,后面粗略的了解了一鳞半爪以后有了大概的印象:"传参数就是值传递,传对象就是引用传递",后面进一步查找了相关资料和文章以后, ...

  3. java开发在线下载功能,自动打开浏览器下载功能下载网络文件或图片

    因为业务的需要,公司产品要求商品详情页面有个下载的功能 找了很多的方法,发现有的需要打开一个新窗口,而且在某些浏览器上不适用,所以继续寻找更好的方法 跟同事沟通后发现他那里有个下载的方法,不过是C#写 ...

  4. java web应用启动报错:Caused by: java.lang.ClassNotFoundException: ServletContext

    ServletContext是个接口,不同的WEB容器(tomcat, jboss等)都有各自的实现. 一般是缺少servlet-api.jar包 在Java Build Path的Libraries ...

  5. 微信小程序开发着工具获取和更新newticket

    newticket是微信开发者工具和微信后台交互的凭证.大多数工具的操作都是需要newticket. 如何获取newticket? 打开开发者工具,依次点击菜单设置->通用设置->代理,使 ...

  6. 设计模式实战系列之@Builder和建造者模式

    前言 备受争议的Lombok,有的人喜欢它让代码更整洁,有的人不喜欢它,巴拉巴拉一堆原因.在我看来Lombok唯一的缺点可能就是需要安装插件了,但是对于业务开发的项目来说,它的优点远远超过缺点. 我们 ...

  7. 看完就能掌握的PHP核心技术 - ​​​​​​​​面向对象

    继承和多态 类的组合与继承 假设我们有两个类,一个 person,另外一个是 family:在 family 类中我们创建 person 类中的对象,并且我们把这个对象视为 family 类的一个属性 ...

  8. jqgrid 自定义文本框、选择框等查询

    要实现jqgrid的自定义查询可通过表格获取查询的条件,再给jqgrid表格发送postData参数. HTML: <table id="querytable" border ...

  9. Java成员变量和局部变量的区别

    定义位置不同: 成员变量:类中方法外 分为类变量(static修饰).实例变量(没有使用static修饰) 局部变量:方法内.代码块内 默认值不同: 成员变量:有默认初始化值 局部变量:没有默认初始化 ...

  10. Linux系统环境下MySQL数据库源代码的安装

    Linux系统环境下MySQL数据库源代码的安装 基本环境:CentOS Linux release 7.8.2003 (Core).MySQL5.6 一.      安装环境准备 若要在Linux系 ...