Johnson 全源最短路 学习笔记

我居然不会这玩意,过来学一下。

算法简介

Johnson 全源最短路 用于求一个带负权的图的任意两点之间的最短路,时间复杂度为 \(\Theta(nm\log m)\)

算法流程

考虑到 \(n\) 次 Dijkstra 的速度比 Floyd 的速度快,所以考虑怎么 \(n\) 次 Dijkstra。我们发现如果有负权边就不能用 Dijkstra,所以考虑怎么把所以边转化成正的。
我们考虑建立一个超级源,向所有点连边权为 \(0\) 的边,然后用 SPFA 计算超级源到所有点的最短路,记做 \(d\)
然后考虑对于一条 \(u/to v\) 的边,边权由 \(w\) 变为 \(w+d_u-d_v\)
我们发现,对于一条路径 \(a_1\to a_2\to a_3\to \dots \to a_k\),这样新的边权下的路径长度为 \(\sum w + d_{a_1}-d_{a_2} + d_{a_2}-d_{a_3} +\dots+ d_{a_{k-1}}-d_{a_k}=\sum w+ d_{a_1}-d_{a_k}\)
我们发现,这个 \(d_i\) 类似于一个势能。这样任意路径长度都和原来的图上的长度一一对应,并且差值只和起终点有关。所以最短路径长度也和原图一一对应。

根据三角形不等式,有 \(d_u+w\ge d_v\),所以新的边权 \(w+d_u-d_v\ge 0\),所以我们可以愉快地使用 \(n\) 次 Dijkstra。
发现前面的 SPFA 不是瓶颈,所以时间复杂度等于 \(n\) 轮 Dijkstra,即 \(\Theta(nm\log m)\)
同时 SPFA 的时候还能顺便检验是否存在负环。

模板题
代码:

#include<queue>
#include<cstdio>
#define db double
#define gc getchar
#define pc putchar
#define U unsigned
#define ll long long
#define ld long double
#define ull unsigned long long
#define Tp template<typename _T>
#define Me(a,b) memset(a,b,sizeof(a))
Tp _T mabs(_T a){ return a>0?a:-a; }
Tp _T mmax(_T a,_T b){ return a>b?a:b; }
Tp _T mmin(_T a,_T b){ return a<b?a:b; }
Tp void mswap(_T &a,_T &b){ _T tmp=a; a=b; b=tmp; return; }
Tp void print(_T x){ if(x<0) pc('-'),x=-x; if(x>9) print(x/10); pc((x%10)+48); return; }
#define EPS (1e-7)
#define INF (0x7fffffff)
#define LL_INF (0x7fffffffffffffff)
#define maxn 3039
#define maxm 10039
#define MOD
#define Type int
#ifndef ONLINE_JUDGE
//#define debug
#endif
using namespace std;
Type read(){
	char c=gc(); Type s=0; int flag=0;
	while((c<'0'||c>'9')&&c!='-') c=gc(); if(c=='-') c=gc(),flag=1;
	while('0'<=c&&c<='9'){ s=(s<<1)+(s<<3)+(c^48); c=gc(); }
	if(flag) return -s; return s;
}
int n,m,u,v,w,head[maxn],nex[maxm],to[maxm],c[maxm],kkk;
#define add(x,y,z) nex[++kkk]=head[x]; head[x]=kkk; to[kkk]=y; c[kkk]=z
int vis[maxn],cnt[maxn]; ll d[maxn];
queue<int> qu;
int SPFA(){
	qu.push(0); int i,j,cur; for(i=1;i<=n;i++) d[i]=LL_INF;
	while(!qu.empty()){
		cur=qu.front(); qu.pop(); vis[cur]=0;
		for(i=head[cur];i;i=nex[i]) if(d[to[i]]>d[cur]+c[i]){
			d[to[i]]=d[cur]+c[i]; if(!vis[to[i]]) vis[to[i]]=1,cnt[to[i]]++,qu.push(to[i]);
			if(cnt[to[i]]>n) return 1;
		}
	}
	for(i=1;i<=n;i++) for(j=head[i];j;j=nex[j]) c[j]=c[j]+d[i]-d[to[j]];
	return 0;
}
struct JTZ{
	int dis,num;
	bool operator < (const JTZ x) const {
		return this->dis > x.dis;
	}
};
ll dis[maxn];
priority_queue<JTZ> q,E;
ll Dij(int st){
	q=E; q.push((JTZ){0,st}); int i; ll s=0; JTZ cur; for(i=1;i<=n;i++) dis[i]=1000000000;
	while(!q.empty()){
		cur=q.top(); q.pop(); if(dis[cur.num]<1000000000) continue;
		dis[cur.num]=cur.dis+d[cur.num]-d[st];
		for(i=head[cur.num];i;i=nex[i]) if(dis[to[i]]==1000000000)
			q.push((JTZ){cur.dis+c[i],to[i]});
	} for(i=1;i<=n;i++) s+=i*dis[i]; return s;
}
int main(){
	n=read(); m=read(); int i; for(i=1;i<=m;i++){ u=read(); v=read(); w=read(); add(u,v,w); }
	for(i=1;i<=n;i++){ add(0,i,0); } if(SPFA()){ puts("-1"); return 0; }
	for(i=1;i<=n;i++) print(Dij(i)),pc('\n');
	return 0;
}
posted @ 2023-02-21 20:35  jiangtaizhe001  阅读(18)  评论(0编辑  收藏  举报