【图论】Johnson算法

适用于求解没有负环的全源最短路,最坏时间复杂度 \(O(nm\log m)\) 比Floyd要优秀(但是Floyd可以找出负环)。
在没有负权边时,使用n次单源最短路Dijkstra代替即可。

算法流程:

1、新建一个虚拟节点(编号为n+1),向[1,n]连接一条边权为0的虚拟边。
2、从n+1号节点开始跑一次队列优化BellmanFord,得到从n+1号虚拟节点到节点i的最短路hgt[i],假如存在负环,则无法求解。
3、删除虚拟节点和虚拟边(也可以直接选择忽视他们)
4、把每条非虚拟边(u,v,w)改为(u,v,w+hgt[u]-hgt[v])
5、对于每个点i属于[1,n],跑一次Dijkstra,得到从i节点到[1,n]的非虚拟节点的最短路dis[j]。
6、原本的[i,j]的最短路为dis[j]+hgt[i]-hgt[j]

最后把边权修改回来的操作可以省去。

namespace Johnson {

    const int MAXN = 2e5 + 10;

    int n;
    vector<pil> G[MAXN];

    ll hgt[MAXN];
    ll dis[MAXN];
    int cnt[MAXN];
    bool vis[MAXN];

    queue<int> Q;
    priority_queue<pli> PQ;

    bool bellmanford() {
        fill(hgt + 1, hgt + 1 + n, LINF);
        fill(cnt + 1, cnt + 1 + n, 0);
        fill(vis + 1, vis + 1 + n, 0);
        while(!Q.empty())
            Q.pop();
        hgt[n] = 0;
        vis[n] = 1;
        Q.push(n);
        while(!Q.empty()) {
            int u = Q.front();
            Q.pop();
            vis[u] = 0;
            ++cnt[u];
            if(cnt[u] == n)
                return false;
            for(pil &p : G[u]) {
                int v = p.first;
                ll w = p.second;
                if(hgt[v] <= hgt[u] + w)
                    continue;
                hgt[v] = hgt[u] + w;
                if(!vis[v]) {
                    vis[v] = 1;
                    Q.push(v);
                }
            }
        }
        return true;
    }

    void dijkstra(int s) {
        fill(dis + 1, dis + 1 + n, LINF);
        fill(vis + 1, vis + 1 + n, 0);
        while(!PQ.empty())
            PQ.pop();
        dis[s] = 0;
        PQ.push({-dis[s], s});
        while(!PQ.empty()) {
            int u = PQ.top().second;
            PQ.pop();
            if(vis[u])
                continue;
            vis[u] = 1;
            for(pil &p : G[u]) {
                int v = p.first;
                ll w = p.second;
                if(vis[v] || dis[v] <= dis[u] + w)
                    continue;
                dis[v] = dis[u] + w;
                PQ.push({-dis[v], v});
            }
        }
    }

    bool johnson() {
        ++n;
        G[n].clear();
        for(int i = 1; i <= n - 1; ++i)
            G[n].eb(i, 0);
        int res = bellmanford();
        G[n].clear();
        --n;
        if(!res)
            return false;
        for(int u = 1; u <= n; ++u) {
            for(pil &p : G[u]) {
                int v = p.first;
                p.second += hgt[u] - hgt[v];
            }
        }
        for(int i = 1; i <= n; ++i) {
            dijkstra(i);
            for(int j = 1; j <= n; ++j) {
                if(dis[j] != LINF)
                    dis[j] -= hgt[i] - hgt[j];
            }
            // do something
        }
        for(int u = 1; u <= n; ++u) {
            for(pil &p : G[u]) {
                int v = p.first;
                p.second -= hgt[u] - hgt[v];
            }
        }
        return true;
    }

}
posted @ 2021-02-10 02:53  purinliang  阅读(734)  评论(0编辑  收藏  举报