zoukankan      html  css  js  c++  java
  • Johnson全源最短路【学习笔记】

    前言

    noip前刷模板时刷到了这个模板,然后随便看了一下题解发现还挺简单的就来把坑补了(虽然好像没什么应用,不补也没关系)

    更好的阅读体验


    模板题

    题意

    给定一个包含 (n) 个结点和 (m) 条带权边的有向图,求所有点对间的最短路径长度,一条路径的长度定义为这条路径上所有边的权值和。

    正文

    Johnson最短路就是用来在稀疏图上解决上述问题的。

    前置芝士:Floyd,Dijkstra,Bellman-Ford,SPFA

    首先我们都学过一个多源最短路的算法:Floyd。它是基于dp的一种做法,详细的我就不说了自己随便上baidu搜一篇看看。它的时间复杂度是稳定的 (O(n^3)),其中 (n) 为点数,下同(看它三重循环也能看出来),这里给出代码实现:

    for (int k = 1; k <= n; k++)
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= n; j++)
                dp[i][j] = min(dp[i][j], dp[i][k] + dp[k][j]);
    

    由于其基于dp的本质所以对于什么样的图都是可做的,但这也有一个缺点:对于任何图复杂度都为 (O(n^3))

    考虑另外一个最短路算法:SPFA(这里就不说 Bellman_Ford 了,不过也默认SPFA的时间复杂度为 (O(nm)),其中 (m) 为边数,下同)。我们知道这是一个单源最短路算法,如果对于每个点作为起点跑一遍SPFA,时间复杂度为 (O(n^2m)),在完全图上被 Floyd 吊打,稀疏图(树)也只是和 Floyd 打平手,还不如就写 Floyd。但它有一个优点:可以跑负权图。这里给出代码实现:

    queue<int> q;
    q.push(s);
    memset(dis, 0x3f, sizeof(dis));
    vis[s] = true;
    dis[s] = 0;
    while (!q.empty()) {
        int x = q.front();
        q.pop();
        for (int i = 0; i < (int)vec[x].size(); i++) {
            #define y vec[x][i].to;
            if (dis[y] > dis[x] + vec[x][i].val) {
                dis[y] = dis[x] + vec[x][i].val;
                if (vis[y] == false) {
                    vis[y] = true;
                    q.push(y);
                }
            }
            #undef y
        } vis[x] = false;
    }
    

    考虑另一个算法:Dijkstra,它是基于贪心的,如果采取堆优化时间复杂度可以到 (O(mlog{n}))。跑 (n) 次复杂度 (O(nmlog{n}))。在稀疏图上完爆上述两个做法。这里该出代码实现:

    priority_queue<pair<int, int> > q;
    q.push(make_pair(s, 0));
    memset(dis, 0x3f, sizeof(dis));
    dis[s] = 0;
    while (!q.empty()) {
        int x = q.top();
        q.pop();
        if (vis[x] == true) continue;
        vis[x] = true;
        for (int i = 0; i <(int)vec[x].size(); i++) {
            #define y vec[x][i].to
            if (dis[y] > dis[x] + vec[x][i].val) {
                dis[y] = dis[x] + vec[x][i].val;
               	q.push(make_pair(y, -dis[y]));
            }
            #undef y
        }
    }
    

    但是这个算法有个缺点:不能处理负权图。为什么?因为他是基于贪心的一个算法,每次从堆顶取出来更新答案的 (x) 必须是已经确定答案的,而如果有负权的话有可能会得到更小的答案,所以就不对了。那么我们考虑如何将这个图变成非负权图。

    想法1

    首先有一个很直观的想法:全部加上某个值,可是这样答案是不正确的,原因是这样找到的最短路的值还和路径经过的边数有关,所以无法保证新图最短路和原图最短路相同。

    想法2

    考虑考虑最短路的性质:(u ightarrow v,dis[v]le dis[u]+val(u,v) Rightarrow val(u,v)+dis[u]-dis[v]ge 0)。这不就是一个非负数吗?那么我们考虑新建一个源点连向每个点一个长度为 (0) 的边,然后跑一遍 SPFA 求出到每个点的最短路 (dis[x]),然后对于每条边 (u ightarrow v),将其权值变为 (val(u,v)+dis[u]-dis[v]),再从每个点跑 Dijkstra 即可。时间复杂度 (O(nm+nmlog{n})) 稀疏图吊打 Floyd。

    完整代码

    #include <bits/stdc++.h>
    using namespace std;
    struct node{
    	int pre, to, val;
    }edge[60005];
    int head[30005], tot;
    int n, m;
    long long dis[30005], D[30005];
    int cnt[30005];
    bool vis[30005];
    queue<int> q;
    priority_queue<pair<long long, int> > pq;
    void add_edge(int u, int v, int l) {
    	edge[++tot] = node{head[u], v, l};
    	head[u] = tot;
    }
    bool SPFA(int s) {
        for (int i = 1; i <= n; i++) dis[i] = 0x3f3f3f3f3f3f3f3f;
        q.push(s);
        vis[s] = true;
        dis[s] = 0;
        cnt[s] = 1;
        while (!q.empty()) {
            int x = q.front();
            q.pop();
            for (int i = head[x]; i; i = edge[i].pre) {
    			int y = edge[i].to; 
                if (dis[y] > dis[x] + edge[i].val) {
                    dis[y] = dis[x] + edge[i].val;
                    cnt[y] = cnt[x] + 1;
                  	if (cnt[y] > n + 1) return false;
                    if (vis[y] == false) {
                        vis[y] = true;
                        q.push(y);
                    }
                }
            } vis[x] = false;
    	}
    	return true;
    }
    int main() {
        scanf("%d%d", &n, &m);
        for (int i = 1; i <= m; i++) {
            int u, v, w;
            scanf("%d%d%d", &u, &v, &w);
            add_edge(u, v, w);
        }
        for (int i = 1; i <= n; i++) add_edge(0, i, 0);
        if (SPFA(0) == false) {
        	puts("-1");
        	return 0;
    	}
        for (int i = 1; i <= n; i++) D[i] = dis[i];
        for (int i = 1; i <= n; i++) {
            for (int j = head[i]; j; j = edge[j].pre) {
                int k = edge[j].to;
                edge[j].val += dis[i] - dis[k];
            }
        }
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= n; j++) dis[j] = 100000000000, vis[j] = false;
            dis[i] = 0;
            while (!pq.empty()) pq.pop();
            pq.push(make_pair(0, i));
            while (!pq.empty()) {
                int x = pq.top().second;
                pq.pop();
                if (vis[x] == true) continue;
                vis[x] = true;
                for (int k = head[x]; k; k = edge[k].pre) {
                    int y = edge[k].to;
                    if (dis[y] > dis[x] + edge[k].val) {
                        dis[y] = dis[x] + edge[k].val;
                        pq.push(make_pair(-dis[y], y));
                    }
                }
            }
            long long ans = 0;
            for (int j = 1; j <= n; j++) {
            	if (dis[j] >= 1000000000)
            		ans = ans + 1ll * j * 1000000000;
    			else
    				ans = ans + 1ll * j * (dis[j] - D[i] + D[j]);
    		}
            printf("%lld
    ", ans);
        }
        return 0;
    }
    
  • 相关阅读:
    (73)C# 扩展方法
    网络
    (十九)守护进程
    (十二)函数返回局部变量
    (十八)WireShark 过滤语法
    (十七)linux网络命令 vconfig ifconfig
    (十六)getsockname()
    (十五)ioctl、ifreq、ifconf
    (十四)UDP协议的两个主要方法sendto和recvfrom详解
    (十三)Packet socket 和 sockaddr_ll
  • 原文地址:https://www.cnblogs.com/zcr-blog/p/14128393.html
Copyright © 2011-2022 走看看