图论:Johnson全源最短路

背景:

Johnson 和 Floyd 一样,是一种能求出无负环图上任意两点间最短路径的算法。该算法在 1977 年由 Donald B. Johnson 提出。
任意两点间的最短路可以通过枚举起点,跑 n次 Bellman-Ford 算法解决,时间复杂度是 O(\(n^2m\))的,也可以直接用 Floyd 算法解决,时间复杂度为 O(\(n^3\))。

注意到堆优化的 Dijkstra 算法求单源最短路径的时间复杂度比 Bellman-Ford 更优,如果枚举起点,跑 n 次 Dijkstra 堆优化算法,就可以在 O(\(nmlogm\))的时间复杂度内解决本问题,
比上述跑 n 次 Bellman-Ford 算法的时间复杂度更优秀,在稀疏图上也比 Floyd 算法的时间复杂度更加优秀。

但 Dijkstra 算法不能正确求解带负权边的最短路,因此我们需要对原图上的边进行预处理,确保所有边的边权均非负。

P5905 【模板】Johnson 全源最短路

点击查看代码块
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<ll,int> pii;

const int inf = 0x7f7f7f7f;
const int maxn = 1e4+10;

int head[maxn],cnt=0;
struct edge{
    int v,next;
    ll w;
}e[maxn<<1];

void add(int u,int v,ll w){
    e[cnt].v=v;e[cnt].w=w;e[cnt].next=head[u];head[u]=cnt++;
}
int n,m,S;//S为超级源点
ll h[maxn];//spfa先跑一次出来超级源点到所有点的单源最短路
int num[maxn];
bool vis[maxn];

bool spfa(int s){
    memset(num,0,sizeof(num));
    memset(vis,0,sizeof(vis));
    for (int i=0;i<=n;i++) h[i] = inf;
    h[s] = 0;
    vis[s] = 1;
    num[s] = 1;
    queue<int> q;
    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;
            ll w=e[i].w;
            if(h[v] > h[u] + w){
                h[v] = h[u] + w;
                if(!vis[v]){
                    vis[v] = 1;
                    num[v]++;
                    q.push(v);
                    if(num[v]>n) return 1;
                }
            }
        }
    }
    return 0;
}

ll dis[maxn];

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

int main(){
    memset(head,-1,sizeof(head));
    cnt = 0;
    cin>>n>>m;
    for (int i=1;i<=m;i++){
        int u,v;
        ll w;
        scanf("%d%d%lld",&u,&v,&w);
        add(u,v,w);
    }
    S = 0;
    for (int i=1;i<=n;i++){//超级源点和所有点连一条边权为0的边
        add(S,i,0);
    }
    if(spfa(S)){
        puts("-1");return 0;
    }
    //更改边权为w+h[u]-h[v]
    for (int u=1;u<=n;u++){
        for (int i=head[u];~i;i=e[i].next){
            int v=e[i].v;
            ll w=e[i].w;
            e[i].w = w + h[u] - h[v];
        }
    }
    //对每个点再跑一次dijkstra
    for (int i=1;i<=n;i++){
        dijkstra(i);
        ll ans = 0;
        for (int j=1;j<=n;j++){
            if(dis[j] == inf) ans += 1ll*j*1e9;
            else ans += 1ll*j*(dis[j]+h[j]-h[i]);//这里注意:原本加上来的现在要减回去
        }
        printf("%lld\n",ans);
    }
    return 0;
}
/*
3 2
1 2 -2
1 3 -3
*/
posted @ 2020-11-21 23:50  wsl_lld  阅读(158)  评论(0编辑  收藏  举报