二进制的应用——枚举子集

集合是指由一个或多个确定的元素所构成的整体,也可以当作不分顺序的数组
当集合中不包含任何元素时,我们称它为空集
我们一般用大括号及期中若干元素表示一个集合,例如 1 , 3 , 5 {1,3,5} 1,3,5表示包含元素1,3,5的一个集合, a , x , a b c {a,x,abc} a,x,abc表示包含三个字符串元素的集合
在集合的元素中没有先后顺序,例如集合 1 , 2 , 3 {1,2,3} 1,2,3和{3,1,2}是等价的
在集合中,有一些集合间的关系,我们这种送会用到一个关系是子集,我们说集合A是集合B的子集,表示A钟所有元素都在B中出现(A集合可以是空集)
例如,对于{1,2,3,4,5},{1,3,5}、{2,5}、{4}都是它的子集,而{1,4,6}不是它的子集
一个长度为n的集合,一共有 2 n 2 2n^2 2n2个子集
对于集合A,可以用一个二进制数来表示集合A的某个子集B。我们用二进制的每一位表示集合A中的每个元素是否在子集B中出现,1表示出现,0表示未出现
对于集合{0,1,2,3,4,5,6}那么二进制 ( 0101101 ) 2 (0101101)_2 (0101101)2就表示子集{0,2,3,5}

二进制数位6543210
{二进制数值0101101
选取的元素-5-32-0

当然,我们还有各种各样的方法来表示子集,但由于计算机内部位运算的便捷性,我们通常借助二进制和位运算完成集合的表示和运算。

例题1:得到整数X

对于 n 个互不相同的正整数,要从这 n 个正整数之中无重复地选取任意个数,使得选出的数的总和为 X。现在你需要求出一共有多少种不同的选取。
对于这道题,我们就可以先枚举这n个数组成的集合的子集,把所有可能的二进制一次枚举,然后根据枚举的二进制对应的子集,从而求出该子集中所有元素的和:
1<<n表示 2 n 2^n 2n
KaTeX parse error: Expected 'EOF', got '&' at position 2: i&̲(1<<j)表示i中的第j位是否为1

#include <iostream>
using namespace std;

int main() {
    int n, x, ans = 0, a[30];
    cin >> n >> x;
    for (int i = 0; i < n; i++) {
        cin >> a[i];
    }
    for(int i=0;i<(1<<n);i++){
        int num=0;
        for(int j=0;j<n;j++){
            if(i&(1<<j)){
                num+=a[j];
            }
        }
        if(num==x){
            ans++;
        }
    }
    cout<<ans<<endl;
    return 0;
}

例题2:李白打酒

话说大诗人李白,一生好饮。
一天,他提着酒壶,从家里出来,酒壶中有酒两斗。他边走边唱:

  • 无事街上走,提壶去打酒。
  • 逢店加一倍,遇花喝一斗。
    这一路上,他一共遇到店 55 次,遇到花 1010 次,已知最后一次遇到的是花,他正好把酒喝光了。请你计算李白有多少种满足要求的遇到店和花的可能情况。

我们已知遇到点5次,遇到花100次,并且最后一次遇到花,正好把酒喝光。我们可以用01串来表示李白一次遇到的是店还是花,用1表示店,用0表示花。
因为已经确定最后一次遇到的是花,所有我们只需要枚举前14次分别是店还是花,根据枚举出的14位01串,判断是否确保刚好有5个1和9个0,并且最后刚好剩一斗酒

#include <iostream>
using namespace std;
int main() {
    int ans = 0;
    for (int i = 0; i < (1 << 14); ++i) {
        int tot_1 = 0;
        int tot_0 = 0;
        int num = 2;
        for(int j=0;j<14;j++){
            if(i&(1<<j)){
                tot_1++;
                num=num*2;
            }
            else{
                tot_0++;
                num=num-1;
            }
        }
        if(tot_1==5 && tot_0==9 && num==1){
            ans++;
        }
    }
    cout << ans << endl;
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值