快速的稠密图 BFS
我们考虑以下问题:给一个 \(n\) 个点的图的补图。补图一共有 \(\mathcal{O}(n)\) 条边。如何尽可能快地完成一次原图上的 BFS(源点任意)?
我们考虑使用 STL 的 unordered_set 来维护这个过程。首先对于每个节点 \(u\),我们插入 \(u\) 不能通往的点。
我们使用一个该容器存储还未到达的点。
开始 BFS。普通的 BFS 中,我们遍历当前点 \(u\) 的每条边进行扩展。在这种特殊的情形下,我们将遍历边改为遍历未到达集合中的点。如果这个集合中的点不在 \(u\) 无法到达的点集中,将 \(u\) 加入队列,并且从未到达点集中删去。
那么,未到达点集中的一个点会贡献它在其它点的无法到达点集中出现的次数 \(+1\) 的时间复杂度,所以我们可以做到 \(\mathcal{O}(n + m) = \mathcal{O}(n)\)。
其实还有第二种稠密图 BFS 方法:压位 BFS。
我们考察普通 BFS 的过程:
取出队头 \(u\):
- 找到 \(u\) 的所有通向的点 \(v\):
如果 \(v\) 已经被遍历过,那么跳过,否则,将其加入队列中。
那么我们知道队列部分的贡献是 \(\mathcal{O}(n)\) 的,遍历邻边的贡献是 \(\mathcal{O}(m)\) 的。考虑从后者下手,如何去掉那些已经无效的边贡献?
既然是稠密图了,那我们尝试邻接矩阵看看。那么复杂度就变成 \(\mathcal{O}(n ^ 2)\) 了。
这样做给我们带来了什么?注意到 BFS 的邻接矩阵是 01 的,这启示我们使用 bitset!
我们使用 \(ar\) 来维护已经遍历到的集合的反集。bitset \(g\) 来存储邻接矩阵。
于是过程变成了:
取出队头 \(u\):
- 找到 \(u\) 的所有通向的点 \(g(u)\),这个是邻接矩阵嘛。
\(g_u\) 和 \(arr\) 按位与得到 \(to\),这是真正要去的点,对 \(to\) 一直执行 lowbit,将这些点加入队列。
分析复杂度,队列和 \(arr\) 维护依旧贡献 \(\mathcal{O}(n)\)。遍历一个点进行两次次按位与,于是是 \(\mathcal{O}(\frac{n^2}{\omega})\)。所以总复杂度是后者。
在 \(n\) 较小时,这个 BFS 的优化效果极为明显,如高次做法中的一次 check。

浙公网安备 33010602011771号