定义
先给出子集的定义:子集是一个数学概念:如果集合A的任意一个元素都是集合B的元素,那么集合A称为集合B的子集。
用途
在写程序的时候,有时候我们可能需要暴力枚举出所有的情况,这时可以考虑通过二进制来枚举子集来尝试解决问题。
解释
假设我们现在有5个小球,上面分别标号了0,1,2,3,4
代表这些小球的权值,现在要像你求出这些小球的权值可以组成的所有情况。
我们用二进制的思维来考虑这个问题,因为有5个小球,所以我们用5个比特位来分别标记小球存在还是不存在,对于这样一种情况,比如我们现在要选择3个小球,分别是0,3,4
号小球,那么我们用二进制1
表是当前的小球存在,用0
表示当前小球不存在
二进制下标 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|
二进制 | 1 | 1 | 0 | 0 | 1 |
小球状态 | 存在 | 存在 | 不存在 | 不存在 | 存在 |
我们可以用5个比特位来表示这种情况,如果小球全部选择的话那么二进制表示就是11111
,二进制的11111
转化为十进制数字就是31
,这个数字正好就是2^5−1,那么我们可以用从0~(2^5−1)这些数表示完所有的选取状态(因为这个范围内的二进制数情况正好包括了这些选取状况).
代码实现
用代码实现非常简单,具体看注释
#include <bits/stdc++.h>
using namespace std;
int main()
{
int n=5;//5个小球
for(int i=0; i<(1<<n); i++) //从0~2^5-1个状态
{
for(int j=0; j<n; j++) //遍历二进制的每一位
{
if(i&(1<<j))//判断二进制第j位是否存在
{
printf("%d ",j);//如果存在输出第j个元素
}
}
printf("\n");
}
return 0;
}
/*运行结果
0
1
0 1
2
0 2
1 2
0 1 2
3
0 3
1 3
0 1 3
2 3
0 2 3
1 2 3
0 1 2 3
4
0 4
1 4
0 1 4
2 4
0 2 4
1 2 4
0 1 2 4
3 4
0 3 4
1 3 4
0 1 3 4
2 3 4
0 2 3 4
1 2 3 4
0 1 2 3 4
*/
例题
例题可以做一下HDU-5616
。
HDU5616 Jam’s balance(二进制枚举子集)
一道习题
看如何用二进制枚举:
话说大诗人李白,一生好饮。幸好他从不开车。
一天,他提着酒壶,从家里出来,酒壶中有酒2斗。他边走边唱:
无事街上走,提壶去打酒。
逢店加一倍,遇花喝一斗。
这一路上,他一共遇到店5次,遇到花10次,已知最后一次遇到的是花,他正好把酒喝光了。
请你计算李白遇到店和花的次序,可以把遇店记为1,遇花记为0
public class 李白打酒二进制 {
public static void main(String[] args) {
// TODO Auto-generated method stub
int ans = 0;
for (int i = 0; i < (1<<14); ++i) {
int tot_1 = 0;//tot_1遇到店的次数
int tot_0 = 0;//tot_0遇到花的次数
int num = 2;
for (int j = 0; j < 14; ++j) {//从0开始,到13,一共14位;查看每一位是0还是1
//j为0时,i&1-->查看的第一位;j为1时i&10-->查看第二位...以此类推
if ((i&(1<<j))==1) { // 这里判断二进制 i 从右数第 j + 1 位是否为 1
tot_1++;
num = num * 2;
} else {
tot_0++;
num = num - 1;
}
}
if (tot_1 == 5 && tot_0 == 9 && num == 1) {//最后一次结果已知,判断前14次即可
++ans; // 记录合法方案数
}
}
}
}