二进制:是计算技术中广泛采用的一种数制。二进制数据是用0和1两个数码来表示的数。它的基数为2,进位规则是“逢二进一”,借位规则是“借一当二”
子集:是一个数学概念:如果集合A的任意一个元素都是集合B的元素,那么集合A称为集合B的子集。
含有
个元素的集合的一切子集的个数为
。简单证明一下:
- 含有0个元素的子集有
个,
- 含有1个元素的子集有
个,
- 含有2个元素的子集有
个,
- .........
- 含有N个元素的子集有
个
由二项式系数的性质可得:
。
我们就是利用了二进制的特性 ,比如说有5个木棍的长度:4, 8, 2, 6, 7。我们就可以用0和1代表选和不选。
如果选出长度为4、2、6的木棍,则如下表:
4 | 8 | 2 | 6 | 7 | |
二进制 | 1 | 0 | 1 | 1 | 0 |
木棍的状态 | 选 | 不选 | 选 | 选 | 不选 |
上面说有5个木棍如果全选的话就是11111对应的十进制数就是31, 这个区间上每一个整数代表一个集合一共
个集合,上表就是数字22(二进制:10110)所代表的集合:4、2、6。
所以我们遍历每一个集合:
for(int i = 0; i < (1 << n); i++)
设s = 13(二进制为1101)代表我们选0 2 3位置上的数值;
那么我们如何找到每个位置上的数值呢?
我们遍历的是二进制的十进制表示(比如13),我们当然可以转化为二进制再枚举每一位,但是,这很麻烦;
一个很巧妙的方式就是利用位运算。
1<<0=1(0);
1<<1=2(10);
1<<2=4(100);
1<<3=8(1000);
1<<4=16(10000);
...
1<<7=128(10000000);
...
看出来了吧!我们只需要将13&(1<<i)我们便可以得到每一位是不是1 (1<< i 除了那一位,剩余的都是0,所以我们就可以得到那一位是不是1)
补充一波位运算的知识吧:
按位与运算符(&)
参加运算的两个数据,按二进制位进行“与”运算。
运算规则:0&0=0; 0&1=0; 1&0=0; 1&1=1;
即:两位同时为“1”,结果才为“1”,否则为0
例如:3&5 即 0000 0011& 0000 0101 = 00000001 因此,3&5的值得1。
左移运算(<<)
a << b就表示把a转为二进制后左移b位(在后面添b个0)。例如100的二进制为1100100,而110010000转成十进制是400,那么100 << 2 = 400。可以看出,a << b的值实际上就是a乘以2的b次方,因为在二进制数后添一个0就相当于该数乘以2(这样做要求保证高位的1不被移出)。
通常认为a << 1比a * 2更快,因为前者是更底层一些的操作。因此程序中乘以2的操作请尽量用左移一位来代替。
因此,我们便有了:
for(int j = 0; j < n; j++)
if(i & (1 << j))
printf(" %d ",a[j]);
那么完整的代码就是:
#include <bits/stdc++.h>
using namespace std;
int main()
{
int n;
cin >> n;
for(int i = 0; i < (1<<n); i++) //从0~2^n-1个状态
{
for(int j = 0; j < n; j++) //遍历二进制的每一位
{
if(i & (1 << j))//判断二进制第j位是否存在
{
printf("%d ",j);//如果存在输出第j个元素
}
}
printf("\n");
}
return 0;
}
例题1:ALGO-115_蓝桥杯_算法训练_和为T点击这里
例题2:HDU5616--Jam's balance点击这里
参考: