传递闭包的概念:
给定一个集合,以及若干元素的传递关系,传递闭包问题是求解所有元素的联通关系,如给定集合 a , b , c {a,b,c} a,b,c,已知 a a a -> b b b, b b b -> c c c,我们可以推出 a a a -> c c c。
如果是借已知单向图推两点间的联通关系,可以用 D F S DFS DFS或 B F S BFS BFS复杂度为 O ( n + m ) O(n + m) O(n+m)。但是求解传递闭包问题时,我们需要求得所有点对之间的联通关系,复杂度就达到了 O ( n ( n + m ) ) O(n(n + m)) O(n(n+m)),在稠密图中,复杂度约等于 O ( n 3 ) O(n^3) O(n3),这么高的复杂度,我们可以用相同复杂度但是代码简单的 F l o y d Floyd Floyd进行处理。
具体方案是用点对间的联通关系来代替原本的路径长度进行递推。
若 d p [ i ] [ k ] = 1 dp[i][k] = 1 dp[i][k]=1并且 d p [ k ] [ j ] = 1 dp[k][j] = 1 dp[k][j]=1(等于 1 1 1表示两节点联通),则说明节点 i i i可以借由节点 k k k到达节点 j j j。
动态转移方程就变成了:
d
p
[
i
]
[
j
]
∣
=
d
p
[
i
]
[
k
]
&
d
p
[
k
]
[
j
]
dp[i][j]\ \ |= \ dp[i][k]\ \&\ dp[k][j]
dp[i][j] ∣= dp[i][k] & dp[k][j]
传递闭包的Floyd代码:
for(int k = 1; k <= n; ++k)
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= n; ++j)
dp[i][j] |= dp[i][k] & dp[k][j];
简单优化一下:先判断 d p [ i ] [ k ] dp[i][k] dp[i][k]是否为 1 1 1再进入 j j j的循环,可以减少一点计算时间。
for(int k = 1; k <= n; ++k)
for(int i = 1; i <= n; ++i)
if(dp[i][k])
for(int j = 1; j <= n; ++j)
if(dp[k][j])
dp[i][j] = 1;
复杂度还是 O ( n 3 ) O(n ^ 3) O(n3)。
但实际上这个代码还能再进行优化,因为 d p dp dp数组中的值只有 0 0 0和 1 1 1。
而且最后一层里, d p [ k ] [ j ] dp[k][j] dp[k][j]为 1 1 1时将 d p [ i ] [ j ] dp[i][j] dp[i][j]赋值为 1 1 1,这个操作其实可以用或操作来取代。所以终极的优化就是使用 b i t s e t bitset bitset,既可以存 0 0 0、 1 1 1,还可以直接进行位运算。
具体代码如下:
bitset<N> dp[N];
void Floyd()
{
for(int k = 1; k <= n; ++k)
for(int i = 1; i <= n; ++i)
if(d[i][k])
d[i] |= d[k];
}
最后复杂度降为了 O ( n 2 ) O(n ^ 2) O(n2),优于直接搜索,可以处理 n = 1000 n = 1000 n=1000的数据量。