• 【洛谷5905】【模板】Johnson 全源最短路


    点此看题面

    • 给定一张(n)个点(m)条边的有向图,可能存在负权边。
    • 求从每个点出发到所有点的最短路。
    • (nle3 imes10^3,mle6 imes10^3),点名卡(n)(SPFA)

    单源最短路算法

    考虑最常见的几个单源最短路算法:(Floyd)(Dijkstra)(SPFA)

    (nle3 imes10^3)(Floyd)简直就是天方夜谭。。。

    有负权边的图没办法跑(Dijkstra)。。。

    题目里点名卡(SPFA),想过除非有着极为惊人的卡常技巧。。。


    所以说,我们就这样束手无策了?

    考虑这三个算法中有一个的失败原因和另两个是不一样的——(Dijkstra)是因为有负权边可能会(WA),而不是会(TLE)

    因此,我们的目标就是要对原图进行一定转化,把所有边权变得非负。

    边权转化

    我们事先建立一个(0)号点,向所有点连一条边权为(0)的边,然后跑一遍(SPFA),求出了从(0)号点到每个点的最短路(h_i)。(这一过程顺便可以把负环判掉)

    然后考虑对于一条有向边(x_i ightarrow y_i)(边权为(v_i)),根据最短路的性质,满足(h_{u_i}+w_ige h_{v_i}),也就是(w_i+h_{u_i}-h_{v_i}ge 0)

    于是,我们把每一条边的边权修改为(w_i+h_{u_i}-h_{v_i}),那么所有边权非负,在这张图上就可以开心地枚举每个起点跑(Dijkstra)了。

    而此时想要转化回原图上(s)(t)的距离,发现加上的(h_{u_i}-h_{v_i})是一个类似于势能的玩意,作为中转节点的贡献会被抵消掉,因此只要给求出的距离减去(h_s-h_t)即可。

    代码:(O(n^2logn))

    #include<bits/stdc++.h>
    #define Tp template<typename Ty>
    #define Ts template<typename Ty,typename... Ar>
    #define Reg register
    #define RI Reg int
    #define Con const
    #define CI Con int&
    #define I inline
    #define W while
    #define N 3000
    #define M 6000
    #define add(x,y,z) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y,e[ee].v=z)
    using namespace std;
    int n,m,ee,lnk[N+5];struct edge {int to,nxt,v;}e[N+M+5];
    int h[N+5],ct[N+5],IQ[N+5];queue<int> Q;I void SPFA()//一遍SPFA预处理
    {
    	#define NA() (puts("-1"),exit(0),0)
    	RI i,k;for(i=1;i<=n;++i) h[i]=1e9;Q.push(n+1);W(!Q.empty())
    		for(i=lnk[k=Q.front()],Q.pop(),IQ[k]=0;i;i=e[i].nxt) h[k]+e[i].v<h[e[i].to]&&
    			(++ct[e[i].to]>2*n&&NA(),h[e[i].to]=h[k]+e[i].v,!IQ[e[i].to]&&(Q.push(e[i].to),IQ[e[i].to]=1));//顺便判负环
    }
    typedef pair<int,int> Pr;int dis[N+5],vis[N+5];priority_queue<Pr,vector<Pr>,greater<Pr> > q;I void Dij(CI s)//Dijkstra跑单源最短路
    {
    	RI i,k,d;for(i=1;i<=n+1;++i) dis[i]=1e9,vis[i]=0;q.push(make_pair(dis[s]=0,s));//从s出发
    	W(!q.empty()) if(k=q.top().second,q.pop(),!vis[k]) for(vis[k]=1,i=lnk[k];i;i=e[i].nxt)
    		(d=dis[k]+e[i].v+h[k]-h[e[i].to])<dis[e[i].to]&&(q.push(make_pair(dis[e[i].to]=d,e[i].to)),0);//给边权加上h[k]-h[e[i].to]使非负
    	long long t=0;for(i=1;i<=n;++i) t+=1LL*i*(i^s?(vis[i]?dis[i]-h[s]+h[i]:1e9):0);printf("%lld
    ",t);//枚举终点减去h[s]-h[i]统计答案
    }
    int main()
    {
    	RI i,x,y,z;for(scanf("%d%d",&n,&m),i=1;i<=m;++i) scanf("%d%d%d",&x,&y,&z),add(x,y,z);
    	for(i=1;i<=n;++i) add(n+1,i,0);for(SPFA(),i=1;i<=n;++i) Dij(i);return 0;//先跑SPFA转化边权,然后枚举每个点出发跑Dijkstra
    }
    
    败得义无反顾,弱得一无是处
  • 相关阅读:
    win10 uwp 异步进度条
    win10 uwp 异步进度条
    win10 uwp 简单MasterDetail
    win10 uwp 简单MasterDetail
    如何使用PHP验证客户端提交的表单数据
    PHP 表单和用户输入讲解
    什么是PHP 面向对象
    PHP 命名空间(namespace)定义
    PHP 魔术常量介绍
    archer 安装
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/JohnsonSP.html
Copyright © 2020-2023  润新知