把“按位 AND 为零”的稠密图邻接关系改写成补集的全部子掩码,并用共享的子掩码状态层把潜在 $O(m2^n)$ 枚举压到 $O(n2^n)$。
前置:位掩码、子掩码、连通分量 DFS/BFS。
原图的邻接判定是 $O(1)$,却可能有平方级边;本节关注的不是加速一次 AND,而是怎样让许多真实点共享一次“枚举某个下闭包”的工作。
给出 $m$ 个互异的 $n$ 位数,$0\le n\le22$、$m\le2^n$。若 $x&y=0$ 就连无向边,求连通分量数。
显式图可能有 $\Theta(m^2)$ 条边;即使对每个点枚举补集子掩码,总工作也可能接近 $3^n$。
建立两个逻辑层:
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$,对应原图边;拼接得到的搜索路径都是原图路径。mask(s) 已从某分量展开,它可到达的全部真实点都已并入该分量。后来再次到达 $s$ 就已经证明两个入口连通,无需重扫其下闭包。每个真实点访问一次;每个辅助掩码访问一次并枚举至多 $n$ 个删位转移。时间 $O(n2^n+m)$,空间 $O(2^n)$,适合 $n\le22$。
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)
辅助子掩码层本质是布尔格上的有向无环图;SOS DP 也在同一格上聚合信息,但这里做的是可达性与连通分量,而不是数值求和。
popcount(x&y)<=1,一个补集下闭包不再精确描述邻居,需要增加缺失位状态或换模型。{1,2,6} 的两层搜索。为什么需要真实层与掩码层两套标记?全局跳过已访问辅助状态为什么不会把两个分量错误合并?
先写显式 $O(m^2)$ 暴力作为随机对拍,再实现两层图。卡 20 分钟后看 Hint 2,先不要看完整伪代码。