bitset 优化 Floyd 传递闭包

Floyd 算法

用于求图中任意两点最短路算法。

Floyd 算法本质是 dp。设 \(i\to j\) 最短路为 \(f_{i,j}\),初始置 \(f_{i,j}\)\(i\to j\) 的边权;若 \(i\to j\) 没有直接连边,置 \(f_{i,j}\)\(+\infty\)

枚举 \(k\),检查 \(i\to k\to j\) 是否比原有的路径长度短,即令

\[f_{i,j}\gets \min(f_{i,j},f_{i,k}+f_{k,j}) \]

\(k\) 遍历过所有点,全局最短路就确定了。

for (int k = 1; k <= n; ++k)
  for (int i = 1; i <= n; ++i)
    for (int j = 1; j <= n; ++j)
      f[i][j] = min(f[i][j], f[i][k] + f[k][i]);

注意 Floyd 算法的枚举顺序是 \((k,i,j)\),更改枚举顺序会导致算法出错。

值得一提,在这篇论文证明,对于 \((i,k,j)\) 的枚举顺序,算法最多跑 \(2\) 遍即可得到正确结果;对于 \((i,j,k)\) 的枚举顺序,算法最多跑 \(3\) 遍即可得到正确结果。

不过,对单个变量的枚举顺序显然没有要求,正序倒序乱序显然都可以。

特别地,假设枚举变量 \(k\) 已经遍历且仅遍历了 \(S\) 中的点,当前答案的意义就是:只经过 \(S\) 中的点作为“中继”,任意两点的最短路。

bitset 优化传递闭包

计算任意两点是否可达。

我们对 dp 数组的含义重新定义,初始若 \(i\to j\) 有边,置 \(f_{i,j}\)\(1\),否者置为 \(0\),特别地,\(i=j\) 时应置为 \(1\)

转移方程为:

\[f_{i,j}\gets f_{i,j}~\operatorname{or}~(f_{i,k}~\operatorname{and}~f_{k,j}) \]

考虑用 bitset 优化。

我们用 bitset 数组作为 dp 数组,\(f_i\) 是一个长 \(N\)bitset

转移方程显然可以看成:

\[f_{i,j}= \begin{cases} f_{i,j}~\operatorname{or} f_{k,j},&\text{if }f_{i,k}=\text{true}\\ f_{i,j},&\text{otherwise} \end{cases} \]

注意到若 \(f_{i,k}\) 为真,则需遍历所有 \(j\)\(f_{i,j}\)\(f_{k,j}\) 取或。

这个过程可以用 bitset 优化。

for (int k = 1; k <= n; ++k)
  for (int i = 1; i <= n; ++i)
    if (f[i][k]) f[i] |= f[k];

由于 bitset 实现上会压位,时间复杂度是 \(O\left(\dfrac{N^3}{w}\right)\)\(w\) 是计算机的位数,一般是 \(32\)\(64\)

开 O2 优化后,编译器可能会进行循环展开,这会使常数变为原来的 \(\dfrac18\sim \dfrac14\)

posted @ 2024-09-12 10:19  weilycoder  阅读(162)  评论(0编辑  收藏  举报