全源最短路(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存的是入队次数, 不是松弛次数