• Johnson 全源最短路


    Johnson 全源最短路#

    Johnson 和 Floyd 一样是能求出无负环图上任意两点间最短路径的算法。

    引入#

    求任意两点间的最短路可以通过枚举起点,跑 n 次 SPFA 来解决,时间复杂度是 O(n2m) 的,也可以用 Floyd 解决,复杂度为 O(n3)

    或者我们可以跑 n 次堆优化的 Dijkstra,复杂度为 O(nmlogm)

    但是 Dijkstra 有一个致命的缺陷就是他不能处理负边权。

    我们不难想到来修改边权使其为正数。

    核心思想#

    我们新建一个虚拟的节点,假设他的编号为 0,从这个点向其他所有点连一条边权为 0 的边。

    接下来我们跑一遍 SPFA,求出零号点到所有点的最短路记为 hi,顺便判断一下有没有负环。

    如果存在一条边 (u,v,w),我们将其修改为 (u,v,w+hihv)

    接下来以每一个点为起点跑 n 边 Dijkstra 就好了。

    复杂度为 O(nmlogm)

    正确性#

    我们考虑找到从 st 的一条路径为:

    sp1p2pkt

    那么这条路径的长度就是:

    (w(s,p1)+hshp1)+(w(p1,p2)+hp1hp2)++(w(pk,t)+hpkht)

    展开就是:

    w(s,p1)+w(p1,p2)++w(pk,t)+hsht

    所以无论怎么走,只要是 st 的一条最短路径,那么最后就是比原答案多了 hsht

    Q:你说的对,但是为什么能保证修改后的边权都是非负数?

    根据 hvhu+w(u,v),稍微变化一下就是 hu+w(u,v)hv0,所以图中的边权均为非负。

    code#

    #include 
    
    #define pii pair
    #define INF 1000000000
    #define int long long
    #define N 10010
    #define endl '\n'
    
    using namespace std;
    
    inline int read()
    {
        int x = 0, f = 1;
        char c = getchar();
        while(c < '0' || c > '9'){if(c == '-') f = -1; c = getchar();}
        while(c <= '9' && c >= '0') x = (x << 1) + (x << 3) + (c ^ 48), c = getchar();
        return x * f;
    }
    
    int n, m, t, head[N], vis[N], cnt[N], h[N], d[N], tot;
    struct node{int v, next, w;}e[N << 4];
    
    inline void add(int u, int v, int w){e[++ tot] = (node){v, head[u], w}; head[u] = tot;}
    
    inline int spfa(int s)
    {
    	queue<int> q;
    	memset(vis, 0, sizeof vis);
    	for(int i = 1; i <= n; i ++) h[i] = INF;
    	h[s] = 0;
    	vis[s] = 1;
    	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, w = e[i].w;
    			if(h[v] > h[u] + w)
    			{
    				h[v] = h[u] + w;
    				if(!vis[v])
    				{
    					vis[v] = 1;
    					cnt[v] ++;
    					q.push(v);
    					if(cnt[v] == n + 1) return 0;
    				}
    			}
    		}
    	}
    	return 1;
    }
    
    inline void dijkstra(int s)
    {
    	priority_queue, greater > q;
    	memset(vis, 0, sizeof vis);
    	for(int i = 1; i <= n; i ++)d[i] = INF;
    	d[s] = 0;
    	q.push({0, 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, w = e[i].w;
    			if(d[v] <= d[u] + w) continue;
    			d[v] = d[u] + w;
    			q.push({d[v], v});
    		}
    	}
        return ;
    }
    
    signed main()
    {
    	n = read(), m = read();
    	for(int i = 1; i <= n; i ++) add(0, i, 0);
    	for(int i = 1; i <= m; i ++)
    	{
    		int u = read(), v = read(), w = read();
    		add(u, v, w);
    	}
    	if(!spfa(0)) return cout << "-1" << endl, 0;//负环输出0
    	for(int u = 1; u <= n; u ++)
    		for(int i = head[u]; i; i = e[i].next)
    			e[i].w += h[u] - h[e[i].v];//修改边权
    	for(int i = 1; i <= n; i ++)
    	{
    		dijkstra(i);
    		int ans = 0;
    		for(int j = 1; j <= n; j ++)
            {
    			if(d[j] == INF) ans += j * INF;
    			else ans += j * (d[j] + h[j] - h[i]);
            }
    		cout << ans << endl;
    	}
    	return 0;
    }
    

    参考文章:https://zhuanlan.zhihu.com/p/99802850

  • 相关阅读:
    方法抽调分类求飞机票
    C语言经典题练习--指针型变量的使用
    GFS分布式文件系统
    NPDP|什么样的产品经理可以被称为优秀?
    redis我记不住的那些命令(三)
    SpringBoot整合redis的基本操作
    [RoarCTF 2019]PHPShe
    文档图像处理:大模型的突破与新探索
    GB/T 26518-2011 高分子增强复合防水片材检测
    使用LVGL提升交互效率:基于启明智显Model3A方案的7寸智能屏用户界面(UI)设计介绍
  • 原文地址:https://www.cnblogs.com/Multitree/p/17740532.html