johnson全源最短路
简介
一下都假设该有向图(无向图同理)有n个点,m条边。
谈及全源最短路,第一个想到的是弗洛伊德算法,简单有效,因为并非本篇文章重点,所以只是把代码放在这里:
int main(){
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
for(int q=1;q<=n;q++)
if(d[j][p]>d[j][i]+d[i][q])
d[j][p]=d[j][i]+d[i][q];
}
唯一要注意的就是必须要枚举转折点。时间复杂度O(n^3)$,当n很大时不是一个可以接受的数字。
或者跑n遍单元最短路。你可以用spfa跑,这样的时间复杂度为\(O(n^2m)\)可以处理负边权。
顺便一提,spfa时间复杂度稀疏图约为\(O(m*2)\),稠密图\(O(m*INF)\)很大一个数
有dijkstra跑的话是\(O(n^2logn)\),加了堆优化,无法处理负边权。
可以发现,以上三种情况都不是全能的,且时间复杂度不够优。所以我们需要一个更有效的方法。
Johnson全源最短路
因为dijkstra是目前时间复杂度最优的,所以用dijkstra跑是最理想的,那么我们需要解决负边权的问题。
第一步:添加一个结点(结点0)向所有结点连一个边权为0的边,从这个点跑一遍spfa,检验是否存在负环,同时得到结点0到所有点的最短路,结点u对于结点0的最短路记为\(h_u\),同时,把起点为u终点为v的边的权值更新为\(w_{u,v}+h_u-h_v\)。因为h数组存的是关于结点0的最短路,多有一定有\(h_v\le h_u+w_{u,v}\) 否则,不等号右边一定可以更新不等号左边,与h数组的定义不符。所以,在做完后所有边的权值变成了非负值,我们可以跑dijkstra。
那么这么做的正确性在哪里?
s到t的路径中随便取出一条 \(𝑠−>𝑝1−>𝑝2−>⋯−>𝑝𝑘−>𝑡\)
则这条路径长度为 \((𝑤_{𝑠,𝑝1}+ℎ_𝑠−ℎ_{𝑝1})+(𝑤_{𝑝1,𝑝2}+ℎ_{𝑝1}−ℎ_{𝑝2})+⋯+(𝑤_{𝑝𝑘,t}+ℎ_{𝑝𝑘}−ℎ_𝑡)\)
化简得到 \(w_{s,p1}+w_{p1,p2}+...+w{pk,t}+h_s-h_t\)
对于从s到t的所有路径,\(h_s,h_t\)是固定的,所以从s到t的最短路在进行了这个操作后最短路路径不变。
第二步:跑dijkstra
第三步:输出答案
记得一定要\(-h_s+h_t\)。
代码
#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<sstream>
#include<queue>
#include<map>
#include<vector>
#include<set>
#include<deque>
#include<cstdlib>
#include<ctime>
#define dd double
#define ll long long
#define ull unsigned long long
#define N 10000
#define M 3010
using namespace std;
const ll INF=1e9;
const ll IINF=0x3f3f3f3f;
int n,m;
struct edge{
int from,to,w,next;
inline void intt(int from_,int to_,int w_,int next_){
from=from_;to=to_;w=w_;next=next_;
}
};
edge li[N];
int head[M],tail;
inline void add(int from,int to,int w){
li[++tail].intt(from,to,w,head[from]);
head[from]=tail;
}
ll h[M];
struct SPFA{
queue<int> q;bool vis[M];
int how_v[M];
inline bool spfa(int u){
while(!q.empty()) q.pop();
memset(h,IINF,sizeof(h));
memset(how_v,0,sizeof(how_v));
memset(vis,0,sizeof(vis));
h[u]=0;q.push(u);vis[u]=1;
while(!q.empty()){
int top=q.front();q.pop();
// printf("%d\n",top);
vis[top]=0;
how_v[top]++;if(how_v[top]==n+1) return 0;
// int k=head[top];printf("%d \n",top);
for(int k=head[top];k;k=li[k].next){
int to=li[k].to;
// printf("%d\n",h[to]);
if(h[to]>h[top]+li[k].w){
h[to]=h[top]+li[k].w;
if(!vis[to]){
q.push(to);vis[to]=1;
}
}
}
}
return 1;
}
};
SPFA s;
struct DIJ{
struct rode{
int sum;
ll d;
rode() {}
rode(int sum,ll d) : sum(sum),d(d) {}
};
struct cmp{
inline bool operator () (rode a,rode b){
return a.d>b.d;
}
};
priority_queue<rode,vector<rode>,cmp> q;
bool vis[M];ll d[N];
inline void dij(int u){
memset(d,IINF,sizeof(d));
while(!q.empty()) q.pop();
memset(vis,0,sizeof(vis));
d[u]=0;q.push(rode(u,d[u]));
while(!q.empty()){
rode fr=q.top();q.pop();
if(vis[fr.sum]) continue;
vis[fr.sum]=1;
for(int k=head[fr.sum];k;k=li[k].next){
int to=li[k].to;
if(d[to]>d[fr.sum]+li[k].w){
d[to]=d[fr.sum]+li[k].w;
q.push(rode(to,d[to]));
}
}
}
}
};
DIJ di;
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
int from,to,w;
scanf("%d%d%d",&from,&to,&w);
add(from,to,w);
}
for(int i=1;i<=n;i++) add(n+1,i,0);
// for(int k=head[n+1];k;k=li[k].next) printf("%d ",li[k].to);
if(!s.spfa(n+1)){
printf("-1");
return 0;
}
// for(int i=1;i<=n+1;i++) printf("%d ",s.how_v[i]);
// while(1);
for(int i=1;i<=m;i++) li[i].w+=h[li[i].from]-h[li[i].to];
for(int i=1;i<=n;i++){
di.dij(i);
ull ans=0;
for(int j=1;j<=n;j++){
if(di.d[j]>INF) ans+=INF*j;
else ans+=(j*(di.d[j]+h[j]-h[i]));
// printf("%lld ",di.d[j]);
}
// while(1);
printf("%lld\n",ans);
}
return 0;
}