Johnson全源最短路
5k_sync_closer · · 个人记录
板子题:https://www.luogu.com.cn/problem/P5905
个人认为是最难的一个最短路算法了,因为实现中用到了很多其他的最短路
数据:
负权边:支持
判断负环:不支持
复杂度:
NM\log M
(实际上是
前置知识
最短路:
原图中的最短路长度:
新图中的最短路长度:
你会发现,带
则新图中最短路长度为:
又因为两点间势能的差为定值,即
所以新图中的最短路与原图中的最短路对应。
实现思路
求0到每个点的最短路,因为图中有负权,可以用
但这样连出来的图一定是个菊花图,选择这两种的效率没什么区别。
而且这不影响最后的时间复杂度,时间复杂度由
然后枚举点u,再枚举所有能到达的点v,更新u到v的权值w。
最后跑
另外,对于这道题,要判断负环。
可以在求0到每个点的最短路时顺便用
代码
#include <iostream>
#include <queue>
#include <utility>
#include <functional>
#include <cstring>
using namespace std;
struct Edge
{
int v, w, nxt;
}edge[9001];
int n, m, u, v, w, head[3001], cnt;long long dis[3001], h[3001], ans;
void add(int u, int v, int w) //加边
{
++cnt;
edge[cnt].v = v;
edge[cnt].w = w;
edge[cnt].nxt = head[u];
head[u] = cnt;
}
void dijkstra(int s) //正常的dijkstra
{
priority_queue<pair<int, int>, vector<pair<int, int> >, greater<pair<int, int> > > q;bool vis[3001];memset(vis, 0, sizeof vis);
for(int i = 1;i <= n;++i) dis[i] = 1e9;
dis[s] = 0;q.push(make_pair(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 = edge[i].nxt)
if(dis[edge[i].v] > dis[u] + edge[i].w)
{
dis[edge[i].v] = dis[u] + edge[i].w;
q.push(make_pair(dis[edge[i].v], edge[i].v));
}
}
}
int main()
{
cin >> n >> m;
for(int i = 0;i < m;++i)
cin >> u >> v >> w, add(u, v, w);
for(int i = 1;i <= n;++i)
add(0, i, 0), h[i] = 1e9; //加从0到每个点的边,权值为0
h[0] = 0;
for(int i = 1;i < n;++i) //开始bellman-ford
for(int u = 0;u <= n;++u)
for(int j = head[u];j;j = edge[j].nxt)
h[edge[j].v] = min(h[edge[j].v], h[u] + edge[j].w);
for(int u = 1;u <= n;++u) //判负环
for(int i = head[u];i;i = edge[i].nxt)
if(h[edge[i].v] > h[u] + edge[i].w)
{
cout << -1;
return 0;
}
for(int u = 1;u <= n;++u)
for(int i = head[u];i;i = edge[i].nxt)
edge[i].w = edge[i].w + h[u] - h[edge[i].v]; //更新权值
for(int u = 1;u <= n;++u)
{
dijkstra(u);ans = 0;
for(int v = 1;v <= n;++v)
if(dis[v] == 1e9) ans += v * 1e9;
else ans += v * (dis[v] - h[u] + h[v]); //还原权值
cout << ans << endl;
}
return 0;
}