随记 – 四月九日 – 利用二进制数高效枚举集合

以下所有操作均可以通过递归DFS每一位并作相应判断实现。但以下写法实现简洁、常数较小,不失为一种优美的技巧。

下文采用SiS_i表示第ii个集合,msk\text{msk}表示其二进制表示。

简单集合运算

  • S1S2msk1msk2S_1 \cup S_2 \leftarrow \text{msk}_1 \mid \text{msk}_2
  • S1S2msk1 & msk2S_1 \cap S_2 \leftarrow \text{msk}_1\space\&\space\text{msk}_2
  • S1S2msk1 & (msk2)S_1 \setminus S_2 \leftarrow \text{msk}_1\space\&\space(\sim\text{msk}_2)
  • (相对于该二进制数类型最大值的)补集msk\sim\text{msk}
  • 对称差集S1ΔS2msk1  msk2S_1 \Delta S_2 \leftarrow {\text{msk}_1}^{\space\land}\space \text{msk}_2

子集与超集

  • 枚举集合SS的所有子集。

    考虑二进制数msk0=S\text{msk}_0=S,依次枚举字典序更小的二进制表示。有msk’(msk1) & msk0\text{msk}’\leftarrow (\text{msk}-1)\space\&\space \text{msk}_0,即取枚举到的集合与SS的交,直到msk=0\text{msk}=0(空集)。显然可以不重不漏地枚举完成所有子集。

    当然,也可以通过lowbit(msk)运算取出集合的所有元素,然后建立[0,2S)[0, 2^{|S|})和子集的映射,枚举连续的二进制数即可。

  • 枚举集合SS的所有超集。

    同理,依次枚举字典序更大的集合。有msk’(msk+1)msk0\text{msk}’\leftarrow (\text{msk}+1)\mid \text{msk}_0,即取枚举到的集合与SS的并集。直到超过给定的某个限定值。

  • 枚举集合SS的所有大小为kk的子集。

    首先通过lowbit(msk)取出SS中所有元素,建立[0,2S)[0, 2^{|S|})对所有子集的映射。

    按照字典序从小到大枚举。具体地,我们以msk=2k1\text{msk}=2^k-1作为第一个枚举的集合,然后依次进行以下操作:

    • l=lowbit(msk)l=\operatorname{lowbit}(\text{msk})p=msk+lp=\text{msk}+l
    • 现在我们得到集合pp,由于ll导致了至少一次进位,故popcount(p)k\operatorname{popcount}(p)\leq k
    • 考虑找出在msk\text{msk}中,紧挨ll的所有更高位的二进制位11。它们都因进位而变成00。因此,有q=msk & pq=\text{msk}\space\&\space \sim p表示上述010 \rightarrow 1的位。
    • 现在我们将这缺失的若干位补足到pp的后面,即msk’=p+q2l\text{msk}’=p+\dfrac{q}{2l},也即将这连续的若干位向右移动到最低位,去除最高位11,并取它们和pp的并。

    本质上,如果ll的二进制表示左侧有c>0c>0个连续的11,则在字典序表示中,在这cc11的左侧状态不变的情况下,msk\text{msk}所表示的已经是能够达到的字典序最大的集合。因此,我们被迫移动(进位)第cc11,在新的状态下,msk’\text{msk}’最低位的cc11就是最小字典序的子集。例如0b1010111000b1011000000b101100011\text{0b1010}{\color{red}1}{\color{blue}11}{00} \rightarrow \text{0b101}{\color{red}1}{\color{green}000}{00} \rightarrow \text{0b101}{\color{red}1}000{\color{blue}{11}}

    1. 1
    2. 2
    3. 3
    4. 4
    5. 5
    6. 6
    for (int msk = (1<<k)-1; msk < (1<<n);) { // 将msk映射回原集合的子集 // ... int l = msk & -msk, p = msk + l; msk = ((msk & ~p) / l >> 1) | p; }
  • 2022年4月9日