Johnson算法
题意简述:给定一个包含 n n n 个结点和 m m m 条带权边的有向图,求全源最短路,可能有负权重的边、重边、自环,部分数据卡 SPFA 算法(还特地针对了SLF优化)
题意很明确,就是卡SPFA、Dijkstra、Floyd的
这题用SPFA可以被卡成 O ( n 2 m ) O(n^2m) O(n2m),Dijkstra不能处理负权重的边,Floyd O ( n 3 ) O(n^3) O(n3)肯定超时
说了这么多,就是为了引出我们要讲的 Johnson 算法
Johnson算法可以判断给定的图中有无负环,在无负环时能给出全源最短路时间复杂度 O ( n m log n ) O(nm\log n) O(nmlogn)
Johnson算法的核心为重新赋予权值,使得边权变成非负的新边权,而在运行过程中以Bellman-Ford和Dijkstra(以下提到的Dijkstra都用优先队列优化)作为自己的子程序
一、算法原理
以下推导过程部分参照《算法导论》
对于给定有向图 G = ( V , E ) G=(V,E) G=(V,E) ,权重函数为 w : E → R w:E \rightarrow R w:E→R ,如果所有的边权重都为非负值,则只需在每一个结点上跑Dijkstra即可,时间复杂度 O ( V E log V ) O(VE\log V) O(VElogV);如果存在边权重为负值的边,但没有负环,则我们只需要预处理出一种新的权重函数 w ′ : E → R w':E \rightarrow R w′:E→R 使得所有的边权重为非负值再跑Dijkstra即可
则新的权重函数 w ′ w' w′ 一定满足以下两个条件:
- 对于结点 u , v ∈ V u,v \in V u,v∈V ,路径 p p p 为使用权重函数 w ′ w' w′ 时 u u u 到 v v v 的一条最短路径,当且仅当 p p p 为使用权重函数 w w w 时 u u u 到 v v v 的一条最短路径
- 对于任何边 ( u , v ) ∈ E (u,v) \in E (u,v)∈E , w ′ ( u , v ) w'(u,v) w′(u,v) 为非负值 (Dijkstra不可以处理负权重的边)
设函数 h : V → R h:V\rightarrow R h:V→R 将结点映射到实数上
对于任何边 ( u , v ) ∈ E (u,v) \in E (u,v)∈E,定义 w ′ ( u , v ) = w ( u , v ) + h ( u ) − h ( v ) w'(u,v) = w(u,v) + h(u) - h(v) w′(u,v)=w(u,v)+h(u)−h(v)
我们先假设图 G G G 没有负环,设路径 p p p 为使用权重函数 w ′ w' w′ 时 v 0 v_0 v0 到 v k v_k vk的一条最短路径 ( v 0 , v k ∈ V ) (v_0,v_k \in V) (v0,vk∈V) ,当且仅当 p p p 为使用权重函数 w w w 时 v 0 v_0 v0 到 v k v_k vk 的一条最短路径
则可以证明 w ′ ( p ) = w ( p ) + h ( v 0 ) − h ( v k ) w'(p) = w(p) + h(v_0)-h(v_k) w′(p)=w(p)+h(v0)−h(vk)
w ′ ( p ) = ∑ i = 1 k w ′ ( v i − 1 , v i ) w'(p)=\sum\limits_{i=1}^{k}{w'(v_{i-1},v_i)} w′(p)=i=1∑kw′(vi−1,vi)
= ∑ i = 1 k ( w ( v i − 1 , v i ) + h ( v i − 1 ) − h ( v i ) ) \qquad \ \ = \sum\limits_{i=1}^{k}{(w(v_{i-1},v_i)+h(v_{i-1})-h(v_i))} =i=1∑k(w(vi−1,vi)+h(vi−1)−h(vi))
= ∑ i = 1 k w ( v i − 1 , v i ) + h ( v 0 ) − h ( v k ) \qquad \ \ =\sum\limits_{i=1}^{k}{w(v_{i-1},v_i)}+h(v_0)-h(v_k) =i=1∑kw(vi−1,vi)+h(v0)−h(vk)
= w ( p ) + h ( v 0 ) − h ( v k ) \qquad \ \ = w(p)+h(v_0)-h(v_k) =w(p)+h(v0)−h(vk)
因为 h h h 为将结点映射到实数上的函数,函数的值不受边权重的影响,所以对于一条为使用权重函数 w w w 时 v 0 v_0 v0 到 v k v_k vk 比 p p p 要长的路径 q q q ,在使用权重函数 w ′ w' w′ 时 q q q 一定比 p p p 长
我们再来看看负环的问题,对于环 c = ⟨ v 0 , v 1 , . . . v k ⟩ c = \langle v_0, v_1, ... v_k\rangle c=⟨v0,v1,...vk⟩ ,其中 v 0 = v k v_0 = v_k v0=vk ,则有 w ′ ( c ) = w ( c ) + h ( v 0 ) − h ( v k ) = w ( c ) w'(c) = w(c) + h(v_0) -h(v_k) = w(c) w′(c)=w(c)+h(v0)−h(vk)=w(c)
则对于 c c c 使用权重函数 w w w 时为负环,当且仅当使用权重函数 w w w 时为负环
那么 h h h 函数该如何处理呢?
我们可以新建一个虚拟结点 s s s ,并将 s s s 和其他所有结点建边权重为 0 0 0 的边,在 s s s 结点跑一次Bellman-Ford(SPFA当然可以),求出 s s s 到其他结点的最短路,则对于任何 v ∈ V v \in V v∈V , h ( v ) h(v) h(v) 的值即为 s s s 到 v v v 的最短路径长,在跑的过程中如果一条路径中某个结点被松弛超过 n n n 次,则一定存在负环,并返回存在负环的信息(SPFA判负环的方法就不延伸了)
因为 s s s 结点入度为 0 0 0 ,所以其他的路径中不会包含 s s s 结点
那么为什么无负环时使用权重函数 w ′ w' w′ ,边权重一定为非负呢?
设现在的图为 G ′ = ( V ′ , E ′ ) G' = (V',E') G′=(V′,E′) , V ′ = V ∪ { s } V' = V \cup \{s\} V′=V∪{s} , E ′ = E ∪ { ( s , v ) , v ∈ E } E' = E \cup \{(s,v),v\in E\} E′=E∪{(s,v),v∈E}
根据三角形不等式,可知对于任意结点 u , v ∈ V ′ u,v \in V' u,v∈V′ ,若 ∃ ( u , v ) ∈ E ′ \exists (u,v) \in E' ∃(u,v)∈E′,则有
h ( u ) + w ( u , v ) ≥ h ( v ) ⇒ w ( u , v ) + h ( u ) − h ( v ) ≥ 0 ⇒ w ′ ( u , v ) ≥ 0 h(u) + w(u,v) \ge h(v) \Rightarrow w(u,v) + h(u) - h(v) \ge 0 \Rightarrow w'(u,v) \ge 0 h(u)+w(u,v)≥h(v)⇒w(u,v)+h(u)−h(v)≥0⇒w′(u,v)≥0
现在我们只需要在每个结点上跑一次Dijkstra即可
时间复杂度 O ( V E log V ) O(VE \log V) O(VElogV) (SPFA时间复杂度最坏 O ( V E ) O(VE) O(VE) ,预处理时间复杂度 O ( V E ) O(VE) O(VE) )
二、参考代码
模板题代码如下 (注:输出按该题要求写的)
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define gc() getchar()
#define pc(a) putchar(a)
#define INF 1000000000
#define MAXN (int)(3e3+15)
template<typename T>void read(T &k)
{
char ch=gc();T x=0,f=1;
while(!isdigit(ch)){if(ch=='-')f=-1;ch=gc();}
while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=gc();}
k=x*f;
}
template<typename T>void write(T k)
{
if(k<0){pc('-');k=-k;}
if(k>9)write(k/10);
pc(k%10+'0');
}
struct Edge
{
int u,v,w,next;
}e[MAXN<<2];
struct node
{
int u,dis;
bool operator<(const node &o)const
{return dis>o.dis;}
};
int n,m,pos=1,head[MAXN];
int h[MAXN],d[MAXN],vis[MAXN],cnt[MAXN],ans;
void addEdge(int u,int v,int w)
{
e[pos]={u,v,w,head[u]};
head[u]=pos++;
}
bool spfa()
{
queue<int> q;
fill(h+1,h+1+n,INF);
q.push(0);
vis[0]=1;h[0]=0;
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;
if(h[v]>h[u]+e[i].w)
{
h[v]=h[u]+e[i].w;
if(!vis[v])
{
q.push(v);
vis[v]=1;
if(++cnt[v]>n) // n+1结点
return 0;
}
}
}
}
return 1;
}
void dijkstra(int st)
{
priority_queue<node> q;
fill(d+1,d+1+n,INF);
memset(vis,0,sizeof(vis));
q.push({st,0});
d[st]=0;
while(!q.empty())
{
int u=q.top().u;q.pop();
if(vis[u])
continue;
vis[u]=1;
for(int i=head[u]; i; i=e[i].next)
{
int v=e[i].v;
if(d[v]>d[u]+e[i].w)
{
d[v]=d[u]+e[i].w;
if(!vis[v])
q.push({v,d[v]});
}
}
}
}
signed main()
{
read(n);read(m);
for(int i=1,u,v,w; i<=m; i++)
{
read(u);read(v);read(w);
addEdge(u,v,w);
}
for(int i=1; i<=n; i++)
addEdge(0,i,0);
if(!spfa())return puts("-1"),0;
for(int i=1; i<pos; i++)
e[i].w+=h[e[i].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[i]-h[j]));
}
write(ans);pc('\n');
}
return 0;
}
转载请说明出处