【零零散散】Floyd算法
Floyd算法的正确性
Floyd算法是一种动态规划算法。常见的模板中,Floyd算法的正确性并不明显,因为常用模板里的是经过状态压缩过的版本,比如这种:
for(int k = 1; k <= n; ++k){ // 枚举中转点 for(int i = 1; i <= n; ++i){ if(i == k) continue; for(int j = i + 1; j <= n; ++j){ if(j == k) continue; dis[i][j] = min(dis[i][j], dis[i][k] + dis[k][j]); } } }
这也使得直接拿着模板看会不太好理解,会产生奇奇怪怪的问题,比如
- 为什么中转点要在最外层枚举
- $dis[i][j] = min(dis[i][j], dis[i][k] + dis[k][j])$ 不一定能得到最短路,为什么是正确的
- ……
要搞定这些问题,首先我们要看Floyd算法的状态代表什么。Floyd算法的状态 $f[i][j][k]$ 代表 “从 $i$ 到 $j$ 的 途中经过的点的最大编号为$k$的 最短的 路径”。因此 $f[i][j][n]$ 就可以表示 $i$ 到 $j$ 的最短路径。从而可以得到状态转移方程:
$$dis[i][j][k] = min(dis[i][j][k-1], dis[i][k][k-1] + dis[k][j][k-1])$$
每次状态转移,都是判断能否通过 $i \rightarrow k$ 和 $k \rightarrow j$ 两条路径拼出比 不含 $k$ 号点时的最短路 更短的一条路。作为一个动态规划,每次更新都是局部最优解。
Floyd算法求两点之间最短路的方案数
/* init: dis <- INF , c <- 0 */ /* edge: dis <- length , c <- 1 */ for(int k=1;k<=n;++k){ for(int i=1;i<=n;++i){ if(i==k)continue; for(int j=i+1;j<=n;++j){ if(j==k)continue; if(dis[i][j]==dis[i][k]+dis[k][j]){ c[j][i]+= c[i][k]*c[k][j]; c[i][j]+= c[i][k]*c[k][j]; } else if(dis[i][j]>dis[i][k]+dis[k][j]){ dis[j][i]=dis[i][j] = dis[i][k]+dis[k][j]; c[j][i]=c[i][j] = c[i][k]*c[k][j]; } } } }
也是动态规划,因为使用的$k$的意义和Floyd相同,所以可以写在一起。