子集
一个包含n个元素的集合,它的子集(包含空集)的个数为个。
这样我们发现,子集的状况可以用二进制的概念进行对照是最直观的。
例如:n=3的集合{,
,
},他的子集和二进制数的关系如下表;
| 子集 | ∅ | |||||||
| 二进制数 | 000 | 001 | 010 | 011 | 100 | 101 | 110 | 111 |
可以看出,每个自己对应一个二进制数,这个二进制数中的每个1都对应着子集中某个元素,而且子集中元素是没有顺序的,这也可以解释为什么子集数量是个了。
下面程序模拟该过程打印出了所有子集:
#include<bits/stdc++.h>
using namespace std;
void subset(int n){
for(int i=0;i<(1<<n);i++){ //1左移n位,为2的n次幂
for(int j=0;j<n;j++) //打印一个子集,即打印i的二进制数中所有1
if(i&(1<<j)) cout<<j<<" "; //从i的最低位开始拒不检查每一位,若为1,打印
cout<<endl;
}
}
int main(){
int n;
cin>>n;
subset(n); //生成n元素所有子集的子函数
}
若打印n个数中任意m个组合,可以对以上方法进行稍微的修改,只要找到符合1个数的子集并打印出来,即为该问题的解。
所以问题转变为查找1的个数为m的二进制数,这些二进制数就是需要打印的子集。
若是逐位判断是否为1的话,则需要检查n次,另有一个更快的方法,他可以直接定位二进制数中1的位置。用到了一个神奇的操作——kk=kk&(kk-1),功能是消除kk的二进制数的最后一个1;连续进行这种操作,每次消除一个1,sum就统计一次,直到原数为0,sum的值就为该二进制数中1的个数。
例如:二进制数1011
1011&(1011-1)=1011&1010=1010; sum=1;
1010&(1010-1)=1010&1001=1000; sum=2;
1000&(1000-1)=1000&0111=0000; sum=3;
end~
则该数里面有3个1。
再简述一下该操作:
(1)用kk=kk&(kk-1)清除kk最后一个1;
(2)sum++;
(3)继续上述操作,直到kk=0;
在树状数组中也有一个类似的操作——lowbit(x)=x&-x,功能是计算x的二进制数的最后一个1;
下面给出求n个数中任意m个数的组合的代码:
#include<bits/stdc++.h>
using namespace std;
void Set(int n,int m){
for(int i=0;i<(1<<n);i++){
int sum=0,kk=i; //sum存放该二进制数多少个1
while(kk){ //kk&(kk-1)操作进行消除1
kk=kk&(kk-1);
sum++;
}
if(sum==m){ //如果是要找的集合,输出
for(int j=0;j<n;j++)
if(i&(1<<j)) cout<<j<<" ";
}
if(sum==m) cout<<endl;
}
}
int main(){
int n,m;
cin>>n>>m;
Set(n,m); //n个数里任意m个数的集合
}
妈妈在也不怕我不会排列组合问题啦~
6970

被折叠的 条评论
为什么被折叠?



