- 给定一张(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
}