赞
踩
Floyd算法是一种求多源汇最短路的算法,它可以求出任意两点间的最短距离(如果这两点连通的话),并且Floyd算法非常容易实现:
for(int k = 1; k <= n; k ++)
for(int i = 1; i <= n; i ++)
for(int j = 1; j <= n; j ++)
d[i][j] = min(d[i][j],d[i][k] + d[k][j]);
它总共有3
层循环,每层n
次,所以时间复杂度为
O
(
n
3
)
O(n^3)
O(n3)
给定一个n
个点m
条边的有向图,图中可能存在重边
和自环
,边权可能为负数
。
再给定k
个询问,每个询问包含两个整数x
和y
,表示查询从点x
到点y
的最短距离,如果路径不存在,则输出impossible
。
数据保证图中不存在负权回路。
第一行包含三个整数n
,m
,k
。
接下来 m 行,每行包含三个整数 x,y,z,表示存在一条从点x到点y的有向边,边长为z。
接下来 k 行,每行包含两个整数 x,y,表示询问点x到点y的最短距离。
共 k 行,每行输出一个整数,表示询问的结果,若询问两点间不存在路径,则输出impossible
。
1
≤
n
≤
200
1≤n≤200
1≤n≤200,
1
≤
k
≤
n
2
1≤k≤n^2
1≤k≤n2
1
≤
m
≤
20000
1≤m≤20000
1≤m≤20000,
图中涉及边长绝对值均不超过
10000
10000
10000。
3 3 2
1 2 1
2 3 2
1 3 1
2 1
1 3
impossible
1
本题题意很简单,给定k个询问,每个询问给出两个点,让我们输出这两点的最短距离,如果这两点不在同一连通块就输出impossible
这正完美契合了Floyd算法,因此我们只要将细节处理一下即可(由于Floyd模板中用到了k,因此代码会将此处的k换成Q)
代码初始化:将
d
[
i
,
i
]
d[i,i]
d[i,i]都初始化为0,其余初始化为+∞,因为从i到i的距离是为0的,要求最小值,所以其它距离刚开始时要为∞,后续才能顺利地更新
#include<iostream> using namespace std; const int N = 210,M = 20010,INF = 0x3f3f3f3f; int n,m,Q; int d[N][N]; // d[i][j] 表示从i到j的距离 void floyd(){ for(int k = 1; k <= n; k ++) for(int i = 1; i <= n; i ++) for(int j = 1; j <= n; j ++) d[i][j] = min(d[i][j],d[i][k] + d[k][j]); } int main(){ scanf("%d%d%d",&n,&m,&Q); // 初始化 for(int i = 1; i <= n; i ++) for(int j = 1; j <= n; j ++){ if(i == j) d[i][j] = 0; else d[i][j] = INF; } // 加边 while(m --){ int a,b,c; scanf("%d%d%d",&a,&b,&c); d[a][b] = min(d[a][b],c); // 两点之间如果有多条边,那么选最短的边即可 } // floyd floyd(); // 处理询问 while(Q --){ int a,b; scanf("%d%d",&a,&b); if(d[a][b] > INF / 2) puts("impossible"); // 边权可能有负数,所以不会直接等于INF else printf("%d\n",d[a][b]); } return 0; }
说是证明,其实不如说是原因,为什么Floyd算法的代码实现是这样的呢?(因为我是先学的板子才学的为什么,所以这里也姑且这么写)
这一切都要从动态规划开始说起。
用闫氏DP分析法:
状态表示:我们用
d
[
k
,
i
,
j
]
d[k,i,j]
d[k,i,j]表示从i走到j,且中间经过的点的编号不超过k的所有路径长度的最小值,什么叫做中间经过点的编号不超过k呢?假设i
到j
中间经过所有点为
a
1
,
a
2
,
.
.
.
,
a
m
a_1,a_2,...,a_m
a1,a2,...,am,那么这m个点的编号值都
<
=
k
<=k
<=k,注意不要与总共经过不超过k个点混淆。
状态计算: d [ k , i , j ] d[k,i,j] d[k,i,j]的计算可以分为两类:
从i走到j,且中间经过的点的编号不超过k且不包含k的所有节点路径:那么它就等价于从i走到j且经过的点的编号不超过k - 1的所有路径的最小值,即 d [ k − 1 , i , j ] d[k - 1,i,j] d[k−1,i,j]
从i走到j,且中间经过的点的编号不超过k且包含k的所有节点路径:那么我们就会发现,它一定会从i走到k,并且一定会从k走到j,因此我们可以将它分为两段之和:
并且前后两段是没有关系的,所以这种情况的表示就是 d [ k − 1 , i , k ] + d [ k − 1 , k , j ] d[k - 1,i,k] + d[k - 1,k,j] d[k−1,i,k]+d[k−1,k,j]
因此,我们可以得出 d [ k , i , j ] = m i n ( d [ k − 1 , i , j ] , d [ k − 1 , i , k ] + d [ k − 1 , k , j ] ) d[k,i,j] = min(d[k - 1,i,j],d[k - 1,i,k] + d[k - 1,k,j]) d[k,i,j]=min(d[k−1,i,j],d[k−1,i,k]+d[k−1,k,j])
优化:我们在01背包中已经知道DP问题的状态转移方程可以降维,那么此刻我们发现状态转移方程的第一维只和
k
,
k
−
1
k,k - 1
k,k−1有关,那么能否把它也降维呢?
只需要看看当计算到
d
[
k
,
i
,
j
]
d[k,i,j]
d[k,i,j]时使用的k是当前的k还是上一个k即可,此时我们考虑特殊情况:当k = j
时,
d
[
k
,
i
,
j
]
=
m
i
n
(
d
[
k
−
1
,
i
,
j
]
,
d
[
k
−
1
,
i
,
j
]
+
d
[
k
−
1
,
j
,
j
]
)
d[k,i,j] = min(d[k - 1,i,j],d[k - 1,i,j] + d[k - 1,j,j])
d[k,i,j]=min(d[k−1,i,j],d[k−1,i,j]+d[k−1,j,j]),由于刚开始初始化时我们将从i到i的距离都初始化为0
了,因此状态转移等价于
d
[
k
,
i
,
j
]
=
m
i
n
(
d
[
k
−
1
,
i
,
j
]
,
d
[
k
−
1
,
i
,
j
]
+
0
)
d[k,i,j] = min(d[k - 1,i,j],d[k - 1,i,j] + 0)
d[k,i,j]=min(d[k−1,i,j],d[k−1,i,j]+0)相当于没有运算,因此每次的
d
[
k
,
i
,
j
]
d[k,i,j]
d[k,i,j]都必然是第k - 1
层的
d
[
k
−
1
,
i
,
j
]
d[k - 1,i,j]
d[k−1,i,j],所以可以将第一维去掉,最终状态转移方程为
d
[
i
,
j
]
=
m
i
n
(
d
[
i
,
j
]
,
d
[
i
,
k
]
+
d
[
k
,
j
]
)
d[i,j] = min(d[i,j],d[i,k] + d[k,j])
d[i,j]=min(d[i,j],d[i,k]+d[k,j])
Floyd可以应用于许多方面。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。