【技巧】补集bfs
前言
众所周知,bfs 可以用于计算边权为 1 的图的单源最短路,并且做到 \(O(n + m)\) 的优秀复杂度。
这是因为在边权为 1 的图中,bfs 每次访问到的点不会再被松弛。所以我们只需要访问每个点每条边各一次。
但是如果我们需要求一张 \(n\) 点 \(m\) 边图补集的单源最短路。我们仍然能做到 \(O(n+m)\) 吗?
推导
如果我们用同样的算法,则补图边的数量级会达到 \(n^2\)。这显然不是我们希望的
考虑手动模拟一下 bfs,因为一般情况下 \(m\) 的数量级与 \(n\) 同阶,我们任选一个点几乎能够与大部分点直接连接。
而已经被访问过的点不会被松弛,再次访问也没有用。也就是说松弛的次数为 \(O(n)\)。
如果原图没有边,则从 \(u\) 每次访问未被松弛的点 \(v\) 则一定会松弛该点。访问未被松弛的点 \(v\) 但是松弛失败当且仅当 \((u, v)\) 在原图出现过。所以松弛失败的次数为 \(O(m)\)
所以,只要我们能够保证不访问已经松弛过的点,则复杂度为 \(O(n+m)\)。
实现
我们考虑维护一个集合 \(S\) 代表未被松弛的节点,每次松弛时,只访问集合 \(S\) 内的点判断能否松弛。并且删除松弛过的节点。
也就是说我们需要维护一个数据结构,支持:
- 遍历所有值
- 删除值
用链表即可。每次枚举 \(u\) 原图的出边 \((u, v)\),把 \(v\) 打上联通标记。代表 \((u,v)\) 这条边不存在。然后访问 \(S\),依次判断能否松弛。最后取消所有 \(v\) 的标记。
复杂度 \(O(n+m)\)。
浙公网安备 33010602011771号