# 高阶扩展:补集子掩码构成的隐式图 ## 本节目标 把“按位 AND 为零”的稠密图邻接关系改写成补集的全部子掩码,并用共享的子掩码状态层把潜在 $O(m2^n)$ 枚举压到 $O(n2^n)$。 ## 前置知识与课前自测 前置:位掩码、子掩码、连通分量 DFS/BFS。 1. $x\&y=0$ 等价于 $y$ 是哪个掩码的子掩码? 2. 为每个 $x$ 枚举其全部邻点,最坏会重复访问一个掩码多少次? 3. 若一个辅助状态已在某连通分量访问过,另一分量还可能通过它发现新点吗?
答案 1. `FULL xor x` 的子掩码。 2. 指数次。 3. 不可能;能由同一辅助状态到达的所有真实点已经与该分量连通。
## 问题动机 原图的邻接判定是 $O(1)$,却可能有平方级边;本节关注的不是加速一次 AND,而是怎样让许多真实点共享一次“枚举某个下闭包”的工作。 ## 精选例题:AND Graph ### 来源、题意与动机 - VJudge:[CodeForces-986C](https://vjudge.net/problem/CodeForces-986C) - 原题:[Codeforces 986C](https://codeforces.com/problemset/problem/986/C) - 难度:2500;考点:补集、子掩码 DAG、隐式连通分量 给出 $m$ 个互异的 $n$ 位数,$0\le n\le22$、$m\le2^n$。若 $x\&y=0$ 就连无向边,求连通分量数。 显式图可能有 $\Theta(m^2)$ 条边;即使对每个点枚举补集子掩码,总工作也可能接近 $3^n$。
Hint 1从真实点 $x$ 跳到辅助状态 `FULL xor x`。
Hint 2辅助状态 $s$ 可向 `s without one set bit` 走;这样恰遍历 $s$ 的全部子掩码。
Hint 3当辅助状态 $s$ 本身是输入值时,再跳回真实点 $s$,继续展开它的补集。
Hint 4真实点与辅助掩码用两套访问标记;每个辅助掩码全局只展开一次。
## Observations 与 Editorial 建立两个逻辑层: - `real(x)`:只对输入集合中的数存在,代表原图顶点; - `mask(s)`:对所有 $0..2^n-1$ 存在,可向删掉任意一个 1 后的状态走。 从 `real(x)` 进入 `mask(FULL xor x)`。沿删位边能且只能到达它的全部子掩码 $y$,这些正是满足 $x\&y=0$ 的候选;若 $y$ 在输入集合,就进入 `real(y)`。所以一次交替的 `real -> mask downset -> real` 精确模拟原图的一条边,反复交替精确模拟原图路径。 从每个尚未访问的真实点启动一次搜索并计数。辅助层虽然使用有向删位边,但只承担“批量枚举下闭包”;原图的对称性由每个真实点都能再次进入自己补集保证。 ### 正确性证明 - **合法性:** 从 `real(x)` 到达的任何真实点 $y$ 都是 `FULL xor x` 的子掩码,因此 $x\&y=0$,对应原图边;拼接得到的搜索路径都是原图路径。 - **完备性:** 对任意原图边 $x-y$,$y\subseteq FULL\oplus x$,删位链必能从该补集到 $y$,所以搜索不会漏掉相邻点,归纳后不漏同分量点。 - **全局剪枝:** 若 `mask(s)` 已从某分量展开,它可到达的全部真实点都已并入该分量。后来再次到达 $s$ 就已经证明两个入口连通,无需重扫其下闭包。 每个真实点访问一次;每个辅助掩码访问一次并枚举至多 $n$ 个删位转移。时间 $O(n2^n+m)$,空间 $O(2^n)$,适合 $n\le22$。 ## 实现指南、核心伪代码与附件 ```text explore(real x): if real x visited: return mark real x explore_mask(FULL xor x) explore_mask(s): if mask s visited: return mark mask s if s is an input value: explore(real s) for each set bit b of s: explore_mask(s xor b) for each input value x not visited: components += 1 explore(real x) ``` [完整 GNU C++17 实现](../../参考实现/03-Bitset/CodeForces-986C.cpp) ## 扩展与联系 辅助子掩码层本质是布尔格上的有向无环图;SOS DP 也在同一格上聚合信息,但这里做的是可达性与连通分量,而不是数值求和。 ## 实现、失效边界 - 两层必须分开标记:辅助掩码可能不是原图顶点。 - 递归深度虽然单次删位不超过 22,但真实层交替可很长;附件用显式栈避免调用栈风险。 - 若邻接条件改成 `popcount(x&y)<=1`,一个补集下闭包不再精确描述邻居,需要增加缺失位状态或换模型。 - 解释为什么复杂度是 $n2^n$ 而不是 $3^n$;再画出 $n=3$、输入 `{1,2,6}` 的两层搜索。 ## 总结与自检 为什么需要真实层与掩码层两套标记?全局跳过已访问辅助状态为什么不会把两个分量错误合并? ## 课后训练建议 先写显式 $O(m^2)$ 暴力作为随机对拍,再实现两层图。卡 20 分钟后看 Hint 2,先不要看完整伪代码。