集合的整数表示(二进制枚举)
原理
对于集合{0,1,2,3…,n-1}的子集:如{0}、{0,1}、{0,1,2 }…
- 总共有2n个:因为C(n,0)+C(n,1)…+C(n,n)=2n,即1<<n个
- 可以用1<<n的二进制码中1与0枚举它们
- 1代表集合中有
- 0代表集合中没有
使用方法
运算规则 | 表示形式 |
---|---|
空集 Φ \Phi Φ | 0 |
只含有第i个元素的集合{i} | 1<<i |
含有全部n个元素的集合{0,1,2…,n-1} | (1<<n)-1 |
判断第i个元素是否属于集合S | if(S&(1<<i)) |
向集合中加入第i个元素S U {i} | S|(1<<i) |
从集合中删除第i个元素S U {i} | S&~(1<<i) |
集合S与集合T的交集S U T | S&T |
集合S与集合T的并集S ∩ \cap ∩ T | S|T |
子集枚举
将集合{0,1,2…,n-1}的子集都枚举出来
for(int S=0;S<1<<n;S++){//枚举所有子集
//对子集的操作,例如输出子集元素
for(int j=0;j<n;j++){//遍历子集二进制的每一位
if(S&1<<j){//若过第j位存在
cout<<j<<endl;//输出第j位
}
}
}
子集的子集的枚举
例如:枚举集合sup{01101100}的子集sub(它的子集如{01100000}、{00101100})
-
像子集枚举一样从0开始不断加1来枚举全部的子集,为了保证集合sub+1是集合sup的子集,就得取交集(sub+1)&sup
但是这会出现(sub+1)&sup仍然是相同的子集
如:sub={00101100}:sub&sup=sub、(sub+1)&sup=sub
-
所以我们可以从从sup开始每次减1直到0为止,为了保证集合sub-1是集合sup的子集,取交集(sub-1)&sup,这样的话可以将sup中所有子集按降序枚举出来,并且(sub-1)&sup会忽略sup中的0而从sub中减去1
int sub=sup;
do{
//对子集的处理
sub=(sub-1)⊃
}while(sub!=sup);//处理完0之后,sub=-1,-1的补码全是1,会有-1&sup=sup
枚举{0,1,……,n}所包含的所有大小为k的子集
代码
按字典序升序枚举出所有满足条件的二进制码
int comb=(1<<k)-1;
while(comb<1<<n){
//这里进行对组合的处理
int x=comb&-comb,y=comb+x;
comb=((comb&~y)/x>>1)|y;
}
如何实现字典序升序
按字典序的话最小的子集为(1<<k)-1,因此从它开始枚举
如何求出comb下一个二进制码
如0101110之后的是0110011
0111110之后的是1001111
- 求出最低位的1开始的连续的1的区间(0101110→0001110)
- 将这一区间全部变为0,并将区间左侧那个0变成1(0101110→0110000)
- 将第1步里取出的区间右移,直到剩下的1的个数减少了1个(0001110→0000011)
- 将第2步和第3步的结果按位取或(0110000|0000011=0110011)
分析程序
1.x=comb&-comb作用
相当于comb&(~comb+1)→取最低位的1
2.y=comb+x
实现第2步将comb最低位开始的1的连续的1的区间变为0并且区间左侧那个0变成1
3.z=comb&~y
实现第1步:得到最低位1开始的连续区间 仔细分析分析,我好难解释啊😭😭
4.d=z/x>>1
实现第3步:将z不断右移,直到最低为1,在移一位
5.comb=d|y
实现第4步
就得到了comb之后的下一个二进制位了,因为从n个元素的集合中进行选择,所以comb的值不能大于等于1<<n
如果那个地方出错了请大佬指出,而且可能出现很多错别字sorry~嘻嘻
(✿◡‿◡) ( •̀ ω •́ )y