问题描述
请例举出加权有向图 G = ( V , E ) G=(V,E) G=(V,E)中每两点之间的最短路径的长度。
输入:
输入按照以下形式给出
∣
V
∣
|V|
∣V∣
∣
E
∣
|E|
∣E∣
s
0
s_0
s0
t
0
t_0
t0
d
0
d_0
d0
s
1
s_1
s1
t
1
t_1
t1
d
1
d_1
d1
…
s
∣
E
∣
−
1
s_{|E|-1}
s∣E∣−1
t
∣
E
∣
−
1
t_{|E|-1}
t∣E∣−1
d
∣
E
∣
−
1
d_{|E|-1}
d∣E∣−1
其中|V|、|E|分别代表图G的顶点数和边数。图G的各顶点编号分别为0, 1, …, |V|-1。
s
i
s_i
si、
t
i
t_i
ti、
d
i
d_i
di分别表示图G第 i 条边(有向)连接的2个顶点的编号以及该边的权值。
输出:
如果图G包含负环(各边权值总和为负数的环),则在1行中输出以下内容
NEGATIVE CYCLE
否则按照以下格式输出路径长度。
D
0
,
0
D_{0,0}
D0,0
D
0
,
1
D_{0,1}
D0,1 …
D
0
,
∣
V
∣
−
1
D_{0,|V|-1}
D0,∣V∣−1
D
1
,
0
D_{1,0}
D1,0
D
1
,
1
D_{1,1}
D1,1 …
D
1
,
∣
V
∣
−
1
D_{1,|V|-1}
D1,∣V∣−1
…
D
∣
V
∣
−
1
,
0
D_{|V|-1,0}
D∣V∣−1,0
D
∣
V
∣
−
1
,
1
D_{|V|-1,1}
D∣V∣−1,1 …
D
∣
V
∣
−
1
,
∣
V
∣
−
1
D_{|V|-1,|V|-1}
D∣V∣−1,∣V∣−1
总共占|V|行。在第 i 行中按顺序输出顶点 i 到各顶点 j 之间最短路径的长度。i 到 j 之间不存在路径时输出INF。相邻数值之间用1个空格隔开。
限制:
1 ≤ |V| ≤ 100
0 ≤ |E| ≤ 9900
−
2
∗
1
0
−
7
-2*10^{-7}
−2∗10−7 ≤
d
i
d_i
di ≤
2
∗
1
0
7
2*10^7
2∗107
图G不存在多重边。
图G不存在自身循环。
输入示例
第一组
4 6
0 1 1
0 2 5
1 2 2
1 3 4
2 3 1
3 2 7
第二组
4 6
0 1 1
0 2 -5
1 2 2
1 3 4
2 3 1
3 2 7
第三组
4 6
0 1 1
0 2 5
1 2 2
1 3 4
2 3 1
3 2 -7
输出示例
第一组
0 1 3 4
INF 0 2 3
INF INF 0 1
INF INF 7 0
第二组
0 1 -5 -4
INF 0 2 3
INF INF 0 1
INF INF 7 0
第三组
NEGATIVE CYCLE
讲解
所有点对间最短路径问题APSP是指以图 G = ( V , E ) G=(V,E) G=(V,E)为对象,求G中每两点之间的最短路径(距离)的问题。如果G中不存在权值为负的边,我们可以将各个顶点作为起点执行|V|次狄克斯特拉算法来求解这类问题。这样做的算法复杂度为 O ( ∣ V ∣ 3 ) O(|V|^3) O(∣V∣3),优先级队列实现的话可以简化至 O ( ∣ V ∣ ( ∣ E ∣ + ∣ V ∣ ) l o g ∣ V ∣ ) O(|V|(|E|+|V|)log|V|) O(∣V∣(∣E∣+∣V∣)log∣V∣)。
在解决APSP问题上,复杂度为 O ( ∣ V ∣ 3 ) O(|V|^3) O(∣V∣3)的弗洛伊德算法广为人知。它不需要G的所有边均非负,只要G不包含环即可正常执行。负环指所有边的权值之和为负的环。这种环可以让两点之间的成本无限缩小,因此无法定义最短路径。
弗洛伊德算法的另一个功能就是判断G中是否存在负环。算法执行结束时,如果G的某顶点v到顶点v(其自身)的最短距离为负,就证明G中存在负环。
弗洛伊德算法让图中各组顶点[i, j]之间的最短路径成本与二维数组元素A[i, j]相对应,然后用动态规划法进行求解。为方便说明,我们设 G = ( V , E ) G=(V,E) G=(V,E)的顶点为{1, 2, 3, …|V|}。
设从顶点 i 出发,仅经由顶点 V k V_k Vk = {1, 2 ,3, …, k}抵达顶点 j 的最短路径成本为 A k [ i , j ] A^k[i,j] Ak[i,j], P k [ i , j ] P^k[i,j] Pk[i,j]为此过程的路径之一。弗洛伊德算法就是依次对k = {1, 2, 3, …|V|}分别递归地计算 A k A^k Ak(即通过 A k − 1 A^{k-1} Ak−1计算 A k A^k Ak),从而最终确定 A ∣ V ∣ A^{|V|} A∣V∣,也就是 A [ i , j ] A[i,j] A[i,j]。
首先, A 0 [ i , j ] A^0[i,j] A0[i,j]表示从 i 到 j 不经由其他任何顶点,所有其值就等于连接 i 与 j 的边的权值。也就是说, A [ i , j ] A[i,j] A[i,j]与图的邻接矩阵相对应,从 i 到 j 存在权值为d的边时 A [ i , j ] = d A[i,j]=d A[i,j]=d,不存在边时 A [ i , j ] = ∞ A[i,j]=\infty A[i,j]=∞。此外,我们设 A [ i , i ] = 0 A[i,i]=0 A[i,i]=0显而易见,此时 A 0 [ i , j ] A^0[i,j] A0[i,j]就是 i 到 j 的最短路径成本。
接下来时k为1, 2, 3, …|V|的情况,我们要通过 A k − 1 A^{k-1} Ak−1来计算 A k A_k Ak。这里我们要分别考虑 P k [ i , j ] P^k[i,j] Pk[i,j]经过点k与不经过点k两种情况。
如果 P k [ i , j ] P^k[i,j] Pk[i,j]不经过顶点k,那就意味着 P k [ i , j ] P^k[i,j] Pk[i,j]只经过端点 i、j、以及属于 V k − 1 V^{k-1} Vk−1 = {1, 2, 3, …, k-1}的顶点,所以此时的 P k [ i , j ] P^k[i,j] Pk[i,j]就相当于 P k − 1 [ i , j ] P^{k-1}[i,j] Pk−1[i,j]。于是这种情况下的 A k [ i , j ] A^k[i,j] Ak[i,j] = A k − 1 [ i , j ] A^{k-1}[i,j] Ak−1[i,j]。
如果 P k [ i , j ] P^k[i,j] Pk[i,j]经过顶点k,则 P k [ i , j ] P^k[i,j] Pk[i,j]会被k分为 i - k和k - j 两个子路径,且这两个子路径全都只经过 V k − 1 V^{k-1} Vk−1 = {1, 2, 3, …, k-1}中的顶点。因此经过k的最短路径的子路径为 P k − 1 [ i , k ] P^{k-1}[i,k] Pk−1[i,k]和 P k − 1 [ k , j ] P^{k-1}[k,j] Pk−1[k,j]。也就是说, A k [ i , j ] A^k[i,j] Ak[i,j] = A k − 1 [ i , k ] A^{k-1}[i,k] Ak−1[i,k] + A k − 1 [ i , j ] A^{k-1}[i,j] Ak−1[i,j]。
综上下式对所有 i、j 均成立
A
k
[
k
,
j
]
A^k[k,j]
Ak[k,j] = min(
A
k
−
1
[
i
,
j
]
A^{k-1}[i,j]
Ak−1[i,j],
A
k
−
1
[
i
,
k
]
A^{k-1}[i,k]
Ak−1[i,k] +
A
k
−
1
[
k
,
j
]
A^{k-1}[k,j]
Ak−1[k,j])
这个算法在求
A
k
[
i
,
j
]
A^k[i,j]
Ak[i,j]时看似需要占用
A
[
i
,
j
,
k
]
A[i,j,k]
A[i,j,k]大小的内存空间
(
O
(
∣
V
∣
3
)
)
(O(|V|^3))
(O(∣V∣3))。或许有人会认为在计算
A
k
[
i
,
j
]
A^k[i,j]
Ak[i,j]的过程中
A
k
−
1
[
i
,
j
]
A^{k-1}[i,j]
Ak−1[i,j]的值会发生变化,但实际上由
A
k
[
k
,
k
]
A^k[k,k]
Ak[k,k] = 0可知
A
k
[
i
,
k
]
A^k[i,k]
Ak[i,k] = min(
A
k
−
1
[
i
,
k
]
A^{k-1}[i,k]
Ak−1[i,k],
A
k
−
1
[
i
,
k
]
A^{k-1}[i,k]
Ak−1[i,k] +
A
k
−
1
[
k
,
k
]
A^{k-1}[k,k]
Ak−1[k,k]) =
A
k
−
1
[
i
,
k
]
A_{k-1}[i,k]
Ak−1[i,k]
A
k
[
k
,
j
]
A^k[k,j]
Ak[k,j] = min(
A
k
−
1
[
k
,
j
]
A^{k-1}[k,j]
Ak−1[k,j],
A
k
−
1
[
k
,
k
]
A^{k-1}[k,k]
Ak−1[k,k] +
A
k
−
1
[
k
,
j
]
A^{k-1}[k,j]
Ak−1[k,j]) =
A
k
−
1
[
k
,
j
]
A_{k-1}[k,j]
Ak−1[k,j]
因此每一组顶点 [ i , j ] [i,j] [i,j]的 A k [ i , j ] A^k[i,j] Ak[i,j]均可以用 A k − 1 [ i , j ] A^{k-1}[i,j] Ak−1[i,j]覆盖, A k [ i , j ] A^k[i,j] Ak[i,j]保存在二维数组中不会影响运算结果。
弗洛伊德算法可通过下述方法实现:
warshallFloyd() //1起点数组
for k = 1 to |V|
for i = 1 to |V|
for j = 1 to |V|
A[i][j] = min(A[i][j], A[i][k] + A[k][j])
AC代码如下
#include<iostream>
#include<algorithm>
#include<vector>
#include<climits>
using namespace std;
static const int MAX = 100;
static const long long INFTY = (1LL<<32);
int n;
long long d[MAX][MAX];
void floyd(){
for(int k = 0; k < n; k++){
for(int i = 0; i < n; i++){
if(d[i][k] == INFTY) continue;
for(int j = 0; j < n; j++){
if(d[k][j] == INFTY) continue;
d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
}
}
}
}
int main(){
int e, u, v, c;
cin>>n>>e;
for(int i = 0; i < n; i++){
for(int j = 0; j < n; j++){
d[i][j] = ( (i == j) ? 0 : INFTY);
}
}
for(int i = 0; i < e; i++){
cin>>u>>v>>c;
d[u][v] = c;
}
floyd();
bool negative = false;
for(int i = 0; i < n; i++) if(d[i][i] < 0) negative = true;
if(negative){
cout<<"NEGATIVE CYCLE"<<endl;
} else {
for(int i = 0; i < n; i++){
for(int j = 0; j < n; j++){
if(j) cout<<" ";
if(d[i][j] == INFTY) cout<<"INF";
else cout<<d[i][j];
}
cout<<endl;
}
}
return 0;
}