【速通】详解Johnson求全源最短路,看不懂私信骂我!

题外话:

        因为滥用LaTeX被喷了,因此学姐把文风换了一换,也是为了大家更好食用嘛!

1,背景板

        众所周知,Dijkstra 算法可以处理无负边权的图,Bellman—Ford 算法则可以处理有负边权的图。

        因此,对于全源最短路,我们可以非常暴力地对每个点都跑一遍 Bellman—Ford。但是,这显然是不够快的,我们注意到堆优化的 Dijkstra 算法比 Bellman—Ford 更优秀。

        于是,一个伟大的想法产生了,把 Dijkstra 的快速和 Bellman—Ford 的负边权适用性结合起来!(~ ̄▽ ̄)~

2,大致思想

        这里直接贴上 Johnson 算法的思路,我们只需要了解流程,而正确性的证明是下一个子目要做的事。

        1,我们先通过设置一个虚拟节点,将它和所有节点间都加一条边权为0的边,很明显,这样对原图是木有任何影响的。

        2,对于仍存在的负边权,我们以虚拟节点为源跑一遍 Bellman—Ford 算法,求得 dis 数组。

        3,接下来,我们要把所有边权变成适合 Dijkstra 宝宝的非负边权,新边权的求法公式如下:

w'(u,v)=w(u,v)+dis[u]-dis[v]

        4,重置边权后,再去对 n 个点跑优化后的 Dijkstra 算法,这里的复杂度是 O(nm logm)

3,正确性的证明

        1,为什么原最短路径和边权重置后的最短路径是一个路径(路径正确性)?

                首先,我们假设在原图中点 x 到点 y 有这么一条路径:

x-p_1-p_2-p_3-......-p_k-y

                那么,原图路径和为下式(式子太长就写了开头和结尾的两项):

w(x,p_1)+dis[x]-dis[p_1]+......+w(p_k,y)+dis[p_k]-dis[y]

                中间的省略部分也就是

\displaystyle\sum _{i=1}^{k-1} w(p_i,p_{i+1})+dis[p_i]-dis[p_{i+1}]

                全部加起来~,我们惊奇的发现结果w(゚Д゚)w

w(x,p_1)+......+w(p_k,y)+dis[x]-dis[y]

                而省略部分是

\displaystyle \sum_{i=1}^{k-1} w(p_i,p_{i+1})

                所以,每条路径上都只加了两个常量,每条路径上的边权和的优劣性是不变的。

        2,为什么新边权保证非负呢?

                首先,有一个灰常简单的不等式:

dis[v]\leq dis[u]+w(u,v)

                毋庸置疑(●ˇ∀ˇ●)

                那么移项一下,再结合刚才所说新边权的求法公式,我们又惊奇地发现:

w'(u,v)=dis[u]-dis[v]+w(u,v)\geq 0

                这样就完美地证明啦!接下来只需要对每一个点跑一遍 Dijkstra 算法即可。

4,对于洛谷模板题

        这道题题目说了有负环的呢,那我们 Johnson 算法的第一步 Bellman-Ford 就可以用来判断负环,直接输出 -1 结束。

        而且题目要求我们每行只输出一个整数即可,因此不用开二维数组,可以求完一轮输出一轮的答案。实际上整个算法并不算很难,只是细节很多,比如函数对于数组的数据共享及繁琐的数组重置,下面贴上 AC 代码。

#include<iostream>
#include<vector>
#include<queue>
#include<climits>
#include<cstring>
#define MAXN 3005
#define int long long
#define INF 1e18
using namespace std;
struct edge{
    int v;
    int w;
};
struct node{
    int dis;
    int u;
    bool operator>(const node& a)const{
        return dis>a.dis;
    }
};
int dis[MAXN];
vector<edge> e[MAXN];
int h[MAXN];
int cnt[MAXN];
bool vis[MAXN];
int n,m;
bool spfa(int s){
    queue<int> q;
    vector<int> dis1(n+2,INF);
    vector<int> cnt1(n+2,0);
    vector<bool> vis1(n+2,false);
    dis1[s]=0;
    q.push(s);
    vis1[s]=true;
    while(!q.empty()){
        int u=q.front();
        q.pop();
        vis1[u]=false;
        for(auto &i:e[u]){
            int v=i.v;
            int w=i.w;
            if(dis1[v]>dis1[u]+w){
                dis1[v]=dis1[u]+w;
                cnt1[v]=cnt1[u]+1;
                if(cnt1[v]>=n+1)return false;
                if(!vis1[v]){
                    q.push(v);
                    vis1[v]=true;
                }
            }
        }
    }
    for(int i=1;i<=n;i++){
        h[i]=dis1[i];
    }
    return true;
}
void reset(){
    for(int i=1;i<=n;i++){
        for(auto &j:e[i]){//划重点,因为涉及修改,一定要加引用
            j.w=j.w+h[i]-h[j.v];
        }
    }
}
void dijkstra(int s){
    priority_queue<node,vector<node>,greater<node>> pq;
    vector<bool> visited(n+1,false);
    for(int i=1;i<=n;i++)dis[i]=INF;
    dis[s]=0;
    pq.push({0,s});
    while(!pq.empty()){
        int u=pq.top().u;
        pq.pop();
        if(visited[u])continue;
        visited[u]=true;
        for(auto &ed:e[u]){
            int v=ed.v,w=ed.w;
            if(dis[v]>dis[u]+w){
                dis[v]=dis[u]+w;
                pq.push({dis[v],v});
            }
        }
    }
}
int read(){
    int s=0,w=1;
    char ch=getchar();
    while(!isdigit(ch)){
        if(ch=='-')w=-1;
        ch=getchar();
    }
    while(isdigit(ch)){
        s=s*10+ch-'0';
        ch=getchar();
    }
    return s*w;
}
signed main(){
    n=read(),m=read();
    for(int i=1;i<=n;i++){
        e[n+1].push_back({i,0});
    }
    int u,v,w_val;
    for(int i=1;i<=m;i++){
        u=read(),v=read(),w_val=read();
        e[u].push_back({v,w_val});
    }
    if(!spfa(n+1)){
        cout<<"-1";
        return 0;
    }
    reset();
    for(int i=1;i<=n;i++){
        dijkstra(i);
        int ans=0;
        for(int j=1;j<=n;j++){
            if(dis[j]>=INF/2){
                ans+=j*1e9;
            }
            else{
                int real_dis=dis[j]-h[i]+h[j];
                ans+=j*real_dis;
            }
        }
        cout<<ans<<"\n";
    }
    return 0;
}

        看不懂来私信骂学姐!

                

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值