全源最短路(Johnson)

\(unodate: 1025/10/15\)

当然不是跑n遍dij的事情

全源最短路算法 \(Johnson\)

题意

给出一个 \(n\) 个点 \(m\) 条边的有向图, 求所有点对间的最短路, 边权可以为负

\(analysis\)

显然如果边权非负就直接跑 \(n\)\(dij\) 完事了 也显然出题人预判了你的预判,不然他出这题干什么

本题最大的难点在于如何解决负环, 这里不买关子直接说

我们尝试将边权转为非负, 一种直接的做法是加上一个固定的数, 但是在 \(dij\) 的过程中我们还不好维护走过的边的数量
于是我们引入一个 势能 :

我们设置一个源点 \(0\) , 暴力 ( \(spfa\) ) 求出其到所有点的最短距离, 然后对于每一条边 \(i\) , 我们将其权值改为 \(val_i + dis[u] - dis[v]\) ,其中 \(dis[x]\) 表示 \(0\)\(x\) 的最短距离, \(val_i\) 表示边 \(i\) 修改前的权值

这样做有一个好处: 当我们计算 \(u, x1, x2, ..., v\) 这一段路的权值时, 有

\[val_{u,x1}+dis[u]-dis[x1]+ val_{x1,x2}+dis[x1]-dis[x2]+ ... + val_{...,v}+dis[...]-dis[v] \]

化简一下成

\[val_{u,v} +dis[u]-dis[v] \]

这是不需要额外维护的, 算是证明了这个方法的可行性

至于为什么 \(val_{u,v}+dis[u]-dis[v]\) 非负

\(dis\) 的定义可知 \(dis[v] \le dis[u]+val_{u,v}\)
变换一下就有 \(val{u,v}+dis[u]-dis[v] \ge 0\)

\(code\)

//May all the beauty be blessed.
#include<bits/stdc++.h>
#define int long long
#define pii pair<int,int>
#define fi first
#define se second
using namespace std;
const int mrx=1e9;
int n,m;
int el,fr[3010];
struct edges{
	int c,v,val;
}e[100010];
void add(int u,int v,int val){
	e[++el].v=v;
	e[el].val=val;
	e[el].c=fr[u];
	fr[u]=el;
}

queue<int> q;
int dis[3010],cnt[3010];
bool v[3010];
bool spfa(){
	for(int i=1;i<=n;i++) dis[i]=mrx;
	q.push(0);
	v[0]=1;
	while(q.size()){
		int u=q.front();
		v[u]=0;
		q.pop();
		for(int i=fr[u];i;i=e[i].c){
			if(dis[e[i].v]>dis[u]+e[i].val){
				dis[e[i].v]=dis[u]+e[i].val;
				
				if(v[e[i].v]) continue;
				v[e[i].v]=1;
				q.push(e[i].v);
				cnt[e[i].v]++;
				if(cnt[e[i].v]>n) return 1;
			}
		}
	}
	return 0;
}

priority_queue<pii,vector<pii>,greater<pii> > p;
int d[3010][3010];
void dij(int x){
	auto &len=d[x];
	for(int i=1;i<=n;i++) len[i]=mrx;
	p.push({0,x});
	
	while(p.size()){
		auto u=p.top();
		p.pop();
		if(len[u.se]<=u.fi) continue;
		len[u.se]=u.fi;
		for(int i=fr[u.se];i;i=e[i].c){
			if(len[e[i].v]>u.fi+e[i].val){
				p.push({u.fi+e[i].val,e[i].v});
			}
		}
	}
}
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	cin>>n>>m;
	for(int i=1;i<=m;i++){
		int u,v,val;
		cin>>u>>v>>val;
		add(u,v,val);
	}
	
	for(int i=1;i<=n;i++) add(0,i,0);
	if(spfa()) return cout<<-1,0;
	
	for(int i=1;i<=n;i++) for(int j=fr[i];j;j=e[j].c) e[j].val+=(dis[i]-dis[e[j].v]);
	
	for(int i=1;i<=n;i++) dij(i);
	
	for(int i=1;i<=n;i++){
		int ans=0;
		for(int j=1;j<=n;j++){
			if(d[i][j]!=mrx) d[i][j]+=(dis[j]-dis[i]);
			ans+=(j*d[i][j]);
		}
		cout<<ans<<'\n';
	}
}

\(details\)

  • 十年OI一场空, 不开____见祖宗
  • n,m写反了? 用反了?
  • cnt存的是入队次数, 不是松弛次数
posted @ 2025-10-15 20:14  破碎中永恒  阅读(3)  评论(0)    收藏  举报