二进制枚举子集(总结+应用)


定义阐明

1.什么是子集:子集是一个数学概念:如果集合A的任意一个元素都是集合B的元素,那么集合A称为集合B的子集。符号语言:若∀a∈A,均有a∈B,则A⊆B。(再说简单点就是 :如果B是A的子集,那么无论B是事件内容是什么、情况是什么,A总包含所的 内容、情况,而其中的 一些 内容、情况 正好就是 B里面的内容、情况)
·
2.枚举:顾名思义,“枚”作为量词,作“个”讲,那么枚举,就是一个一个的列举,如果一件事情(出现的所有情况)能够被一个一个的列举,那么它的数量肯定就是有限的,否则是不能被一一列举出来的。所以 把一件事情的所有可能列举出来就叫枚举:
·
3.为什么在二进制下 枚举:一个数在二进制只有 0和1两个数组成,例如3的二进制位位 0011 在二进制下3有4位数组成,我们可以 认为 某一位 如果是这一位0则某个事件没有发生、某个物品没有被选用,如果这一位是1则认为某个事件发生了、某个物品被使用了,这样二进制下的每一位 就代表某件事情发生与否、某个事物的使用与否。因此我们可以用(0~2^n)来表示 n件事的发生与否、n个物品的选用与否,从而枚举出了所有的情况。
·
4.二进制枚举的用途: 普通的枚举 就如 用dfs爆搜不行吗,对于数据量小的时候是可以用的,但对较大的数据量,我们就可以考虑 二进制枚举 来解题。


简单的知识铺垫

这里我们需要了解一些简单的位运算知识

知识1: 1<<n(相当与 2^n)把1的二进制下0001点每一位 左移位n为
例如:1<<3 左移3位后为在二进制位 1000,则1变成了8
知识2: &位运算符在两个二进制的相对应的某一位中中,如果都是1,则改为为1,否则为0
例如:1100&0101 返回的结果为 0101、0&1的结果0、1&1点结果为1.


应用举例

我们可以假设有5个小球,小球的编号是 0、1、2、3、4、5,没个小球均可以被选用或者不被选用,现在 要求你 列出所有的小球使用情况的二进制表示,
这个时候我们就 可以在二进制的思想下考虑这个问题:假设我们默认 二进制下的1是选用小球,二进制下的0是不选用某个小球,我们总共有5 个小球,则总共的选用情况 有2^5 = 32种因此我们可以用一个0~31之间的十进制数字,每个数字代表一种选用组合情况

小球编号 4 3 2 1 0
对应二进制位 5 4 3 2 1
1的二进制数 0 0 0 0 1
是否选用该位置小球
···
10的二进制数 0 1 0 1 0
是否选用该位置小球
···
31的二进制数 1 1 1 1 1
是否选用该位置小球

通过这个表格,我们可以看出每种情况种的每个小球是否被选用的状态(这里需要思考 用来表示十进制数的范围是[0,2^n)

代码实现
#include<iostream>
using namespace std;
int main()
{
    int n = 5;     //有5个小球
    for(int i = 0;i < 1<<n;i ++)    //遍历0~31之间的所有数字(遍历32种情况)
    {
        for(int j = 0;j < n;j ++)   //遍历某个 十进制数的 所有二进制的每一位
        {
            if(i & 1<<j)            //检查二进制数的某一位 是否为 1(小球是否被使用)
            {
                cout<<"1";          //被使用输出1
            }
            else
                cout<<"0";          //否则输出0
        }
        cout<<endl;
    }


    return 0;
}
/*
输出结果:
00000
10000
01000
11000
00100
10100
01100
11100
00010
10010
01010
11010
00110
10110
01110
11110
00001
10001
01001
11001
00101
10101
01101
11101
00011
10011
01011
11011
00111
10111
01111
11111
*/

例题应用

秤取物体重量
Description
题意很简单:给n个砝码的重量,然后给一个天平,然后给一个物体的重量,问你当前拥有的砝码能否把物体的重量秤出来

例如:

给3个砝码重量:1 2 3

给一个物重:5 的物体

显然用重量为 2 3 的砝码可以秤出

Input
第一行输入一个正整数n(1<=n<=20)

第二行输入n个数,代表砝码重量

第三行输入一个正整数m代表询问次数

接下来输入m个数,代表物重

Output
如果可以秤出物体重量,输出YES,反则输出NO

Sample Input 1

3
1 2 3
3
1
7
5
Sample Output 1
YES
NO
YES
Hint
思路如下:

首先要知道 天平的两边都可放秤砣,剩下的就是 遍历出每一种 秤砣的组合情况,这个时候直接爆搜会炸,所以可以选用 二进制枚举 列举出所有组合情况。

题解如下:
#include<iostream>
using namespace std;

int main()
{
    int n;
    cin>>n;
    int ar[25];     //存储秤砣
    for(int i=0;i<n;i++)
        cin>>ar[i];
    int br[2005] = {0};   //桶排思想,对每一种方案 产生的组合重量进行标记
    for(int i = 0;i < (1<<n) ; i++)     //二进制枚举
    {
        int cur = 0;
        for(int j = 0; j < n; j++)	//遍历查看二进制下i的每一位
        {
            if(i & (1<<j))	//二进制下某一位为1,则表示这一位所代表的秤砣被使用
                cur += ar[j];	//秤砣总重量增加
        }
        br[cur] = 1;		
        for(int k = 0; k < n; k++)	//遍历 拿走已经使用放在左边的秤 或者 把没有使用过的秤砣放右边 
        {
            if(cur - ar[k]>0)
            {
                br[cur - ar[k]] = 1;
                cur -= ar[k];		//每一次都是在原来的基础上进行修改操作,产生新的秤砣组合使用情况(这一步不要忘)
            }
        }
    }

    int t;
    cin>>t;
    while(t--)
    {
        int temp;
        cin>>temp;
        if(br[temp])
            cout<<"YES\n";
        else
            cout<<"NO\n";
    }
    return 0;
}

其它例题

传送门1
传送门2
传送门3


发布了73 篇原创文章 · 获赞 100 · 访问量 2711

猜你喜欢

转载自blog.csdn.net/qq_34261446/article/details/103522369