Johnson 全源最短路

Aisaka_Taiga發表於2023-10-06

Johnson 全源最短路

Johnson 和 Floyd 一樣是能求出無負環圖上任意兩點間最短路徑的演算法。

引入

求任意兩點間的最短路可以透過列舉起點,跑 \(n\) 次 SPFA 來解決,時間複雜度是 \(O(n^2 m)\) 的,也可以用 Floyd 解決,複雜度為 \(O(n^3)\)

或者我們可以跑 \(n\) 次堆最佳化的 Dijkstra,複雜度為 \(O(nm\log m)\)

但是 Dijkstra 有一個致命的缺陷就是他不能處理負邊權。

我們不難想到來修改邊權使其為正數。

核心思想

我們新建一個虛擬的節點,假設他的編號為 \(0\),從這個點向其他所有點連一條邊權為 \(0\) 的邊。

接下來我們跑一遍 SPFA,求出零號點到所有點的最短路記為 \(h_{i}\),順便判斷一下有沒有負環。

如果存在一條邊 \((u,v,w)\),我們將其修改為 \((u,v, w+h_{i}-h_{v})\)

接下來以每一個點為起點跑 \(n\) 邊 Dijkstra 就好了。

複雜度為 \(O(nm \log m)\)

正確性

我們考慮找到從 \(s\)\(t\) 的一條路徑為:

\[s\to p1 \to p2 \to \dots \to pk \to t \]

那麼這條路徑的長度就是:

\[(w(s,p1)+h_{s} - h_{p1}) + (w(p1,p2) + h_{p1}-h_{p2}) + \dots + (w(pk, t) + h_{pk} - h_{t}) \]

展開就是:

\[w(s, p1) + w(p1,p2) + \dots + w(pk,t) + h_{s} - h_{t} \]

所以無論怎麼走,只要是 \(s\to t\) 的一條最短路徑,那麼最後就是比原答案多了 \(h_{s}-h_{t}\)

Q:你說的對,但是為什麼能保證修改後的邊權都是非負數?

根據 \(h_{v}\le h_{u} + w(u,v)\),稍微變化一下就是 \(h_{u} + w(u,v) - h_{v} \ge 0\),所以圖中的邊權均為非負。

code

#include <bits/stdc++.h>

#define pii pair<int, int>
#define INF 1000000000
#define int long long
#define N 10010
#define endl '\n'

using namespace std;

inline int read()
{
    int x = 0, f = 1;
    char c = getchar();
    while(c < '0' || c > '9'){if(c == '-') f = -1; c = getchar();}
    while(c <= '9' && c >= '0') x = (x << 1) + (x << 3) + (c ^ 48), c = getchar();
    return x * f;
}

int n, m, t, head[N], vis[N], cnt[N], h[N], d[N], tot;
struct node{int v, next, w;}e[N << 4];

inline void add(int u, int v, int w){e[++ tot] = (node){v, head[u], w}; head[u] = tot;}

inline int spfa(int s)
{
	queue<int> q;
	memset(vis, 0, sizeof vis);
	for(int i = 1; i <= n; i ++) h[i] = INF;
	h[s] = 0;
	vis[s] = 1;
	q.push(s);
	while(!q.empty())
	{
		int u = q.front();
		q.pop();
		vis[u] = 0;
		for(int i = head[u]; i; i = e[i].next)
		{
			int v = e[i].v, w = e[i].w;
			if(h[v] > h[u] + w)
			{
				h[v] = h[u] + w;
				if(!vis[v])
				{
					vis[v] = 1;
					cnt[v] ++;
					q.push(v);
					if(cnt[v] == n + 1) return 0;
				}
			}
		}
	}
	return 1;
}

inline void dijkstra(int s)
{
	priority_queue<pii, vector<pii>, greater<pii> > q;
	memset(vis, 0, sizeof vis);
	for(int i = 1; i <= n; i ++)d[i] = INF;
	d[s] = 0;
	q.push({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 = e[i].next)
		{
			int v = e[i].v, w = e[i].w;
			if(d[v] <= d[u] + w) continue;
			d[v] = d[u] + w;
			q.push({d[v], v});
		}
	}
    return ;
}

signed main()
{
	n = read(), m = read();
	for(int i = 1; i <= n; i ++) add(0, i, 0);
	for(int i = 1; i <= m; i ++)
	{
		int u = read(), v = read(), w = read();
		add(u, v, w);
	}
	if(!spfa(0)) return cout << "-1" << endl, 0;//負環輸出0
	for(int u = 1; u <= n; u ++)
		for(int i = head[u]; i; i = e[i].next)
			e[i].w += h[u] - h[e[i].v];//修改邊權
	for(int i = 1; i <= n; i ++)
	{
		dijkstra(i);
		int ans = 0;
		for(int j = 1; j <= n; j ++)
        {
			if(d[j] == INF) ans += j * INF;
			else ans += j * (d[j] + h[j] - h[i]);
        }
		cout << ans << endl;
	}
	return 0;
}

參考文章:https://zhuanlan.zhihu.com/p/99802850

相關文章