集合的整数表示

本文转自《挑战程序设计竞赛》 - p156 - 专栏: 集合的整数表示

集合的整数表示

在做一些算法题的时候经常需要表示一些集合的状态,如状压dp。
在程序中表示集合的方法有很多种,很直观的可以用数组直接模拟。
当元素个数较小时,可以考虑使用二进制码来表示(不超过32位时可以用int,不超过64时可以用long long)。

集合{0,1, … ,n-1}的子集S可以用如下方式编码成整数:
f(S) = Σ(2^i) (i∈S)

像这样表示后,一些集合运算可以对应写成如下形式:

基本操作

  1. 空集: 0
  2. 只含有第i个元素的集合{i}: 1<<i;
  3. 含有全部n个元素的集合{0,1,…,n-1}: (1<<n)-1
  4. 判断第i个元素是否属于集合S: if (S>>i & 1)
  5. 向集合中加入第i个元素S∪{i}: S | (1<<i)
  6. 从集合中去除第i个元素S\{i}: S & ~(1<<i)
  7. 集合S和T的并集S∪T: S|T
  8. 集合S和T的交集S∩T: S&T

枚举子集

  1. 枚举{0, 1, …, n-1}的全部子集
for (int S = 0 ; S < 1 << n; S++ ) {
    // 对子集的处理
}

按照这个顺序进行循环的话,S便会从空集开始循环,然后按照{0}、{1}、{0,1}、…、{0,1,…,n-1}的升序枚举出来。

  1. 枚举某个指定集合sup的子集
int sub = sup;
do {
    // 对子集处理
    sub = (sub-1) & sup;
} while(sub != sup); // 处理完之后 会有 -1&sup = sup
  1. 枚举{0,1,…,n-1}所有大小为k的子集(有k个元素)
int comb = (1 << k) -1;
while ( comb < 1 << n ) {
    // 对大小为k的集合的处理
    int x = comb & -comb;
    int y = comb + x;
    comb = ((comb & ~y) / x >> 1) | y;
}

按照字典序,最小的大小为k的子集是(1<<k)-1,所以用它做为初始集合。
现在求出comb后的下一个大小为k的子集的二进制码。
例如:
0010 1110 之后的是 0011 0011, 0011 1110之后的是0100 1111
下面是求出comb下一个二进制码的方法:
(1). 求出最低位的1开始的连续的1的区间(0010 1110 -> 0000 1110)
(2). 将这一区间全部变为0,并将区间左侧的那个0变为1(0010 1110 -> 0011 0000)
(3). 将第(1)步里取出的区间(连续1的那个)右移,直到剩下的1的个数减少了1个(0000 1110 -> 0000 0011),也就是遇到第一个1移走就可以结束了。
(4). 将第(2)步和第(3)步的结果按位取或(0011 0000 | 0000 0011 -> 0011 0011)

这里有个技巧,对于非零的整数,x & (-x)的值就是将其最低位的1独立出来之后的值。

将最低位的1取出后,设它为x。那么通过计算y=comb+x,就将comb从最低位为1开始的连续的1都置0了。我们来比较一下~y和comb。在comb中加上x后没有变化的位,在~y中全部取相反的值。而最低位1开始的连续区间在~y中依然是1,区间左侧的那个0在~y中也依然是0。于是通过计算z=comb&~y就得到了最低位1开始的连续区间。比如,如果comb = 0010 1110,则x = 0000 0010, y = 0011 0000, z = 0000 1110

同时,y也恰好是第(2)步要求的值。那么首先将z不断右移,直到最低位为1,这通过计算z/x即可完成。这样再将z/x右移1位就得到了第(3)步要求的值。这样我们就求得了comb之后的下一个二进制列。因为是从n个元素的集合中进行选择,所以comb的值不能大于等于1<<n。如此一来,就完成了大小为k的所有子集的枚举。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值