2024.3.24 【最后死去的人,是最不幸的。】
Sunday 二月十五
P5905 【模板】全源最短路(Johnson)
//2024.3.24
//by white_ice
#include<bits/stdc++.h>
using namespace std;
#define itn int
const int oo = 5003;
const int inf = 1e9;
struct nod{int v,w,nxt;}st[oo<<1];
int head[oo],top;
void add(int x,itn y,int w){
top++;
st[top].w = w;
st[top].v = y;
st[top].nxt = head[x];
head[x] = top;
}
struct node{
int dis,id;
bool operator<(const node &a)const {return dis>a.dis;}
node(itn d,itn x){dis=d,id = x;}
};
itn n,m;
itn h[oo],t[oo];
bool vis[oo];
bool spfa(int s) {
queue<int> q;
memset(h, 63, sizeof(h));
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=st[i].nxt){
int v = st[i].v;
if (h[v]>h[u]+st[i].w) {
h[v]=h[u]+st[i].w;
if (!vis[v]) {
vis[v] = 1;
q.push(v);
t[v]++;
if (t[v] == n+1)
return false;
}
}
}
}
return true;
}
itn dis[oo];
void dijkstra(int s){
priority_queue<node> q;
for (int i=1;i<=n;i++)
dis[i] = inf;
memset(vis, 0, sizeof(vis));
dis[s] = 0;
q.push(node(0, s));
while (!q.empty()) {
int u = q.top().id;
q.pop();
if (vis[u])
continue;
vis[u] = 1;
for (int i=head[u];i;i=st[i].nxt) {
int v = st[i].v;
if (dis[v]>dis[u]+st[i].w) {
dis[v]=dis[u]+st[i].w;
if (!vis[v])
q.push(node(dis[v], v));
}
}
}
return;
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin >> n >> m;
for (int i=1;i<=m;i++){
int u, v, w;
cin >> u >> v >> w;
add(u, v, w);
}
for (int i=1;i<=n;i++)
add(0, i, 0);
if (!spfa(0)){
cout << -1 ;
return 0;
}
for (int u=1;u<=n;u++)
for (int i=head[u];i;i=st[i].nxt)
st[i].w+=h[u]-h[st[i].v];
for (int i=1;i<=n;i++){
dijkstra(i);
long long ans = 0;
for (int j = 1; j <= n; j++) {
if (dis[j] == inf)
ans += j * inf;
else ans += j * (dis[j] + h[j] - h[i]);
}
cout << ans << endl;
}
return 0;
}
全源最短路(Johnson)
Johnson 和 Floyd 一样,是一种能求出无负环图上任意两点间最短路径的算法。该算法在 1977 年由 Donald B. Johnson 提出。
任意两点间的最短路可以通过枚举起点,跑 n 次 Bellman–Ford 算法解决,时间复杂度是 O(n^2m) 的,也可以直接用 Floyd 算法解决,时间复杂度为 O(n^3)。
注意到堆优化的 Dijkstra 算法求单源最短路径的时间复杂度比 Bellman–Ford 更优,如果枚举起点,跑 n 次 Dijkstra 算法,就可以在 O(nm\log m)(取决于 Dijkstra 算法的实现)的时间复杂度内解决本问题,比上述跑 n 次 Bellman–Ford 算法的时间复杂度更优秀,在稀疏图上也比 Floyd 算法的时间复杂度更加优秀。
但 Dijkstra 算法不能正确求解带负权边的最短路,因此我们需要对原图上的边进行预处理,确保所有边的边权均非负。
一种容易想到的方法是给所有边的边权同时加上一个正数 x,从而让所有边的边权均非负。如果新图上起点到终点的最短路经过了 k 条边,则将最短路减去 kx 即可得到实际最短路。