二进制集合操作

数学 / binary-set

本地源文件:docs/math__binary-set.md

二进制集合操作

前置知识:位运算整数与位序列

一个数的二进制表示可以看作是一个集合(00 表示不在集合中,11 表示在集合中).比如集合 {1,3,4,8}{1,3,4,8},可以表示成 (100011010)2(100011010)2.而对应的位运算也就可以看作是对集合进行的操作.

操作集合表示位运算表示
交集𝑎 ∩𝑏a∩b𝑎AND⁡𝑏aAND⁡b
并集𝑎 ∪𝑏a∪b𝑎OR⁡𝑏aOR⁡b
补集¯𝑎a¯NOT⁡𝑎NOT⁡a(全集为二进制都是 1)
差集𝑎 ∖𝑏a∖b𝑎AND⁡NOT⁡𝑏aAND⁡NOT⁡b
对称差𝑎△𝑏a△b𝑎XOR⁡𝑏aXOR⁡b

在进一步介绍集合的子集遍历操作之前,先看位运算的有关应用例子.

模 2 的幂

一个数对 22 的非负整数次幂取模,等价于取二进制下一个数的后若干位,等价于和 𝑚𝑜𝑑 −1mod−1 进行与操作.

C++Python

---|---

---|---

于是可以知道,22 的非负整数次幂对它本身取模,结果为 00,即如果 𝑛n 是 22 的非负整数次幂,𝑛n 和 𝑛 −1n−1 的与操作结果为 00

事实上,对于一个正整数 𝑛n,𝑛 −1n−1 会将 𝑛n 的最低 11 位置零,并将后续位数全部置 11.因此,𝑛n 和 𝑛 −1n−1 的与操作等价于删掉 𝑛n 的最低 11 位.

借此可以判断一个数是不是 22 的非负整数次幂.当且仅当 𝑛n 的二进制表示只有一个 11 时,𝑛n 为 22 的非负整数次幂.

C++Python

---|---

---|---

子集遍历

遍历一个二进制数表示的集合的全部子集,等价于枚举二进制数对应掩码的所有子掩码.

掩码是一串二进制码,用于和源码进行与运算,得到屏蔽源码的若干输入位后的新操作数.

掩码对于源码可以起到遮罩的作用,掩码中的 11 位意味着源码的相应位得到保留,掩码中的 00 位意味着源码的相应位进行置 00 操作.将掩码的若干 11 位改为 00 位可以得到掩码的子掩码,掩码本身也是自己的子掩码.

给定一个掩码 𝑚m,希望有效迭代 𝑚m 的所有子掩码 𝑠s,可以考虑基于位运算技巧的实现.

---|---

或者使用更紧凑的 for 语句:

---|---

这两段代码都不会处理等于 00 的子掩码,要想处理等于 00 的子掩码可以使用其他办法,例如:

---|---

接下来证明,上面的代码访问了所有 𝑚m![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 的子掩码,没有重复,并且按降序排列.

假设有一个当前位掩码 𝑠s![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7),并且想继续访问下一个位掩码.在掩码 𝑠s![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 中减去 11![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7),等价于删除掩码 𝑠s![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 中最右边的设置位,并将其右边的所有位变为 11![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7).

为了使 𝑠 −1s−1![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 变为新的子掩码,需要删除掩码 𝑚m![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 中未包含的所有额外的 11![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 位,可以使用位运算 `(s - 1) & m` 来进行此移除.

这两步操作等价于切割掩码 𝑠 −1s−1![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7),以确定算术上可以取到的最大值,即按降序排列的 𝑠s![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 之后的下一个子掩码.

因此,该算法按降序生成该掩码的所有子掩码,每次迭代仅执行两个操作.

特殊情况是 𝑠 =0s=0![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7).在执行 𝑠 −1s−1![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 之后得到 −1−1![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7),其中所有位都为 11![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7).在 `(s - 1) & m` 操作之后将得到新的 𝑠s![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 等于 𝑚m![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7).因此,如果循环不以 𝑠 =0s=0![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 结束,算法的循环将无法终止.

使用 popcount(𝑚)popcount(m)![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 表示 𝑚m![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 二进制中 11![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 的个数,用这种方法可以在 𝑂(2popcount(𝑚))O(2popcount(m))![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 的时间复杂度内遍历集合 𝑚m![](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 的子集.

### 遍历所有掩码的子掩码

在使用状压 DP 的问题中,有时会希望对于每个掩码,遍历掩码的所有子掩码:

---|---

这样做可以遍历大小为 𝑛n 的集合的每个子集的子集.

接下来证明,该操作的时间复杂度为 𝑂(3𝑛)O(3n),𝑛n 为掩码总共的位数,即集合中元素的总数.

考虑第 𝑖i 位,即集合中第 𝑖i 个元素,有三种情况:

  • 在掩码 𝑚m 中为 00,因此在子掩码 𝑠s 中为 00,即元素不在大小子集中.
  • 在 𝑚m 中为 11,但在 𝑠s 中为 00,即元素只在大子集中,不在小子集中.
  • 在 𝑚m 和 𝑠s 中均为 11,即元素同时在大小子集中.

总共有 𝑛n 位,因此有 3𝑛3n 个不同的组合.

还有一种证明方法是:

如果掩码 𝑚m 具有 𝑘k 个 11,那么它有 2𝑘2k 个子掩码.对于给定的 𝑘k,对应有 (𝑛𝑘)(nk) 个掩码 𝑚m,那么所有掩码的总数为:

𝑛∑𝑘=0(𝑛𝑘)2𝑘∑k=0n(nk)2k

上面的和等于使用二项式定理对 (1 +2)𝑛(1+2)n 的展开,因此有 3𝑛3n 个不同的组合.

参考资料

本页面主要译自博文Перебор всех подмасок данной маски 与其英文翻译版 Submask Enumeration.其中俄文版版权协议为 Public Domain + Leave a Link;英文版版权协议为 CC-BY-SA 4.0.

习题

本页面最近更新: 2026/1/27 12:26:08,更新历史 发现错误?想一起完善?在 GitHub 上编辑此页! 本页面贡献者:Tiphereth-A, aofall, arielherself, c-forrest, gavinliu266, Great-designer, hhc0001, jol888, Menci, shawlleyw, TOMWT-qwq, ZnPdCo 本页面的全部内容在CC BY-SA 4.0SATA 协议之条款下提供,附加条款亦可能应用