【技巧】补集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)\)

posted on 2025-12-11 18:57  Evan_song  阅读(5)  评论(0)    收藏  举报