筆記-Johnson全源最短路
前言
noip前刷模板時刷到了這個模板,然後隨便看了一下題解發現還挺簡單的就來把坑補了(雖然好像沒什麼應用,不補也沒關係)
題意
給定一個包含 \(n\) 個結點和 \(m\) 條帶權邊的有向圖,求所有點對間的最短路徑長度,一條路徑的長度定義為這條路徑上所有邊的權值和。
正文
Johnson最短路就是用來在稀疏圖上解決上述問題的。
前置芝士:Floyd,Dijkstra,Bellman-Ford,SPFA
首先我們都學過一個多源最短路的演算法:Floyd。它是基於dp的一種做法,詳細的我就不說了自己隨便上baidu搜一篇看看。它的時間複雜度是穩定的 \(O(n^3)\)
for (int k = 1; k <= n; k++)
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
dp[i][j] = min(dp[i][j], dp[i][k] + dp[k][j]);
由於其基於dp的本質所以對於什麼樣的圖都是可做的,但這也有一個缺點:對於任何圖複雜度都為 \(O(n^3)\)。
考慮另外一個最短路演算法:SPFA(這裡就不說 Bellman_Ford 了,不過也預設SPFA的時間複雜度為 \(O(nm)\)
queue<int> q; q.push(s); memset(dis, 0x3f, sizeof(dis)); vis[s] = true; dis[s] = 0; while (!q.empty()) { int x = q.front(); q.pop(); for (int i = 0; i < (int)vec[x].size(); i++) { #define y vec[x][i].to; if (dis[y] > dis[x] + vec[x][i].val) { dis[y] = dis[x] + vec[x][i].val; if (vis[y] == false) { vis[y] = true; q.push(y); } } #undef y } vis[x] = false; }
考慮另一個演算法:Dijkstra,它是基於貪心的,如果採取堆優化時間複雜度可以到 \(O(m\log{n})\)。跑 \(n\) 次複雜度 \(O(nm\log{n})\)。在稀疏圖上完爆上述兩個做法。這裡該出程式碼實現:
priority_queue<pair<int, int> > q;
q.push(make_pair(s, 0));
memset(dis, 0x3f, sizeof(dis));
dis[s] = 0;
while (!q.empty()) {
int x = q.top();
q.pop();
if (vis[x] == true) continue;
vis[x] = true;
for (int i = 0; i <(int)vec[x].size(); i++) {
#define y vec[x][i].to
if (dis[y] > dis[x] + vec[x][i].val) {
dis[y] = dis[x] + vec[x][i].val;
q.push(make_pair(y, -dis[y]));
}
#undef y
}
}
但是這個演算法有個缺點:不能處理負權圖。為什麼?因為他是基於貪心的一個演算法,每次從堆頂取出來更新答案的 \(x\) 必須是已經確定答案的,而如果有負權的話有可能會得到更小的答案,所以就不對了。那麼我們考慮如何將這個圖變成非負權圖。
想法1
首先有一個很直觀的想法:全部加上某個值,可是這樣答案是不正確的,原因是這樣找到的最短路的值還和路徑經過的邊數有關,所以無法保證新圖最短路和原圖最短路相同。
想法2
考慮考慮最短路的性質:\(u\rightarrow v,dis[v]\le dis[u]+val(u,v) \Rightarrow val(u,v)+dis[u]-dis[v]\ge 0\)。這不就是一個非負數嗎?那麼我們考慮新建一個源點連向每個點一個長度為 \(0\) 的邊,然後跑一遍 SPFA 求出到每個點的最短路 \(dis[x]\),然後對於每條邊 \(u\rightarrow v\),將其權值變為 \(val(u,v)+dis[u]-dis[v]\),再從每個點跑 Dijkstra 即可。時間複雜度 \(O(nm+nm\log{n})\) 稀疏圖吊打 Floyd。
完整程式碼
#include <bits/stdc++.h>
using namespace std;
struct node{
int pre, to, val;
}edge[60005];
int head[30005], tot;
int n, m;
long long dis[30005], D[30005];
int cnt[30005];
bool vis[30005];
queue<int> q;
priority_queue<pair<long long, int> > pq;
void add_edge(int u, int v, int l) {
edge[++tot] = node{head[u], v, l};
head[u] = tot;
}
bool SPFA(int s) {
for (int i = 1; i <= n; i++) dis[i] = 0x3f3f3f3f3f3f3f3f;
q.push(s);
vis[s] = true;
dis[s] = 0;
cnt[s] = 1;
while (!q.empty()) {
int x = q.front();
q.pop();
for (int i = head[x]; i; i = edge[i].pre) {
int y = edge[i].to;
if (dis[y] > dis[x] + edge[i].val) {
dis[y] = dis[x] + edge[i].val;
cnt[y] = cnt[x] + 1;
if (cnt[y] > n + 1) return false;
if (vis[y] == false) {
vis[y] = true;
q.push(y);
}
}
} vis[x] = false;
}
return true;
}
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);
add_edge(u, v, w);
}
for (int i = 1; i <= n; i++) add_edge(0, i, 0);
if (SPFA(0) == false) {
puts("-1");
return 0;
}
for (int i = 1; i <= n; i++) D[i] = dis[i];
for (int i = 1; i <= n; i++) {
for (int j = head[i]; j; j = edge[j].pre) {
int k = edge[j].to;
edge[j].val += dis[i] - dis[k];
}
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) dis[j] = 100000000000, vis[j] = false;
dis[i] = 0;
while (!pq.empty()) pq.pop();
pq.push(make_pair(0, i));
while (!pq.empty()) {
int x = pq.top().second;
pq.pop();
if (vis[x] == true) continue;
vis[x] = true;
for (int k = head[x]; k; k = edge[k].pre) {
int y = edge[k].to;
if (dis[y] > dis[x] + edge[k].val) {
dis[y] = dis[x] + edge[k].val;
pq.push(make_pair(-dis[y], y));
}
}
}
long long ans = 0;
for (int j = 1; j <= n; j++) {
if (dis[j] >= 1000000000)
ans = ans + 1ll * j * 1000000000;
else
ans = ans + 1ll * j * (dis[j] - D[i] + D[j]);
}
printf("%lld\n", ans);
}
return 0;
}