Johnson 全源最短路
全源最短路,换一种说法就是n个单源最短路,可以用n次Bellman-Ford或SPFA,非负边权还可以用Dijkstra,可是有负边权用前两个算法还是慢,如果我们能把负边权映射成非负边权的话,一切就都好办了
这里我们引入一个虚拟结点,它和所有点的初始距离都是0,然后,我们求出来这个结点和其他店的最短路h,然后,我们将各个边边权变为w+h[u]-h[v](如果最短路不经过u,也就是不满足松弛操作h[v]>h[u]+w,如果经过u,h[v]=h[u]+w,所以一定非负)
然后,我们就可以用n次Heap-Dijkstra来求了
时间复杂度O(nm+n(mlogn))(SPFA+n*Heap-Dijkstra)
与floyed相比,此算法适合用在稀疏图
例题 洛谷 P5905 【模板】Johnson 全源最短路
#include<iostream>
#include<vector>
#include<queue>
#include<utility>
#include<cstring>
#define forup(i,l,r) for(int i=l;i<=r;i++)
using namespace std;
typedef long long ll;
const ll INF=1e9;
struct edge{ int v,w; };
vector<edge> node[3005];
int d[3005];
int n,m;
int h[3005];
queue<int> q;
bool ex[3005];
int cnt[3005];//判负环用的,见SPFA算法
int SPFA(int s)
{
memset(h,127,sizeof(h));
h[s]=0; q.push(s); ex[0]=true;
while(!q.empty())
{
int u=q.front();
for(auto x:node[u])
{
int v=x.v,w=x.w;
if(h[u]+w<h[v])
{
h[v]=h[u]+w;
if(!ex[v]) {
q.push(v); ex[v]=true;
cnt[v]=cnt[u]+1;
if(cnt[v]>n) return -1;
}
}
}
q.pop(); ex[u]=false;
}
return 0;
}
void dijkstra(int s)
{
priority_queue<pair<int,int>> q;
bool vis[3005]={};
forup(i,1,n) d[i]=INF;
d[s]=0;
q.push({0,s});
while(!q.empty())
{
int u=q.top().second; q.pop();
if(vis[u]) continue;
else vis[u]=true;
for(auto x:node[u])
{
int v=x.v,w=x.w;
if(d[u]+w<d[v]) d[v]=d[u]+w;
q.push({-d[v],v});
}
}
}
int main()
{
cin>>n>>m;
int u,v,w;
forup(i,1,m)
{
cin>>u>>v>>w;
node[u].push_back({v,w});
}
forup(i,1,n) node[0].push_back({i,0});
if(SPFA(0)==-1){//判负环的
cout<<-1;
return 0;
}
forup(u,1,n)
{
for(auto &x:node[u])
{
x.w+=h[u]-h[x.v];//映射
}
}
forup(u,1,n)
{
dijkstra(u);
ll ans=0;
forup(v,1,n)
if(d[v]==INF) ans+=INF*v;
else ans+=(ll)(d[v]-h[u]+h[v])*v;
cout<<ans<<'\n';
}
return 0;
}