二进制集合运算

Posted headchen

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了二进制集合运算相关的知识,希望对你有一定的参考价值。

二进制集合运算


如果通过【二进制】按位运算的方式来进行【集合】操作,则会给算法带来简洁、快速等特点。是【状压DP】中常用的【状态】表示形式。

  • 如果【集合】元素的取值只有【两种可能】最好,如果有多种可能,可能需要多【位】来表示【一个元素】
  • “最好”从0【位】开始而不是从【1】开始。

[ { 2,4,5 } ightarrow 110overbrace{fbox{1}}^{包含 2 }0underbrace{ fbox{0}}_{不包含0} ag{1}]

集合的表示

集合 c++ 代码 备注
(E) :全集 $(1<< n) -1 $ $ 2^n -1 = underbrace{(111dots111)}_{共n位} $
({k}:第k个元素) $ 1 << k$ (00dotsoverbrace{fbox{1}}^{第k位 }dots00)
$sim S = E - S $ :补集 $ e & (sim s) (|绝对补集 | |)phi ; 或者 {} $ :空集 $0 $

集合的基本运算

集合操作 c++代码 备注
(S+{k}) $ s (1 << k)(|把第k位设置为 1 | |)S-{k}$

集合常用的操作

集合操作 c++代码 备注
$[k in S] $ $ (s gg k ) & 1(| S是否包含k ? | |) lowbit(S)$ $ s & (-s)$
$ S =sum_{kin S} 1 = sum_{k=0}^{n-1}[kin S]$
([|S|=1]) $ lowbit(s)==s (| S 是否只有1个元素 ? | |)S oplus T$ $ s wedge t (| 【异或】操作,参见下面的解释 | |)S oplus {k}$

集合的异或
: 异或也称为【对称差】
[S oplus T = (S cup T )-(S cap T) = (S-T) cup (T-S)]

文氏图:

[axorb.png-2.2kB][1]
[1]: http://static.zybuluo.com/headchen/y9pkjazwkfcgpjij5kp83zp3/axorb.png

代码为 s ^ t,所需要把第k位取反,则 代码为 s ^ (1 << k) 即可。

二进制集合压缩的方式枚举子集

【枚举子集】的返回结果是一个包含了所有【子集】的集合,然后可以遍历这个【集合】执行某个操作,一般情况下是“边枚举,边执行”,这样可以省略空间,但不方便数学上的表示。
数学上称为【幂集】,表示为[ P(S) = { x | x ? S } ={ phi, {1}, {2}, {3}, {1,2}, {1,3}, {2,3}, {1,2,3} dots}]

  1. 注意:一定不能忽略【空集】
  2. 因为每一个元素均有“选择”“不选择”两种可能,根据【乘法原理】,那么选择的方法就有 (2^n) 种(包括空集),也就是说(|P(S)| = 2^k)
  1. 忽略【顺序】的枚举
    (Bin(i)) 表示 i 的二进制压缩集合。则:
    [P(S) = igcup_{i=0}^n Bin(i)]

在实际的代码中:

 for(int i=0;i<(1<<n);i++)
 {
    DoSomeThing(i);  //这里i代表“压缩为i”的集合。
 }

递归枚举【子集】的方法(一)

  1. 在很多【搜索】算法中,枚举的顺序是“按照【回溯】算法”的顺序枚举,则必须用递归式来定义:
    (P(S,j)) 表示 “从第j个元素开始” 的所有的元素的【子集】:

[P(S,j) = {j} cup P(S,j+1);igcup ; P(S,j+1)]

其含义是:以第j个元素是否【包含】进行分类枚举,可以看出和【组合计数】中的递推式很像。对这个数学公式进行递归求解,就是【回溯】枚举子集:

//n:原集合中有n个元素,begin:从第j开始,s表示 结果集合中的“一个子集”
void Subset(int n, int begin, vector<int> &chosen)
{
    if (begin > n)
        return;
    chosen.push_back(begin);   //选择begin ,产生了一个新的子集,调用 dosomething
    doSomething(chosen);
    Subset(n, begin + 1, chosen);  //递归调用
    chosen.pop_back();   //恢复现场

    Subset(n, begin + 1, chosen);  //不选择,直接调用
}


void Subset(int n)
{
    vector<int> chosen;
    doSomething(vector); //处理空集
    Subset(n, 1, chosen);
}

递归枚举子集的方法(二)

(P(S,j)) 表示 “从第j个元素开始” 的所有的元素的【子集】:

[P(S,j) = igcup_{k=j}^n ig( {j} cup P(S,k+1) ig) ]

其含义是:从j开始,给子集“增加一个元素,从而产生新的子集,有多少种方法?”的形式进行分类枚举,对这个数学公式进行递归求解,就是【回溯】枚举子集:

void doSomething(vector<int> &chosen)
{
}
void Subset(int n, int begin, vector<int> &chosen)
{
    //【空集】也算一个,每次调用都产生新的【子集】
    doSomething(chosen);
    if (begin > n)
        return;
    //从begin 开始,增加一个元素有多少种方法?
    for (int i = begin; i<=n; i++)
    {
        chosen.push_back(i);
        Subset(n, i + 1, chosen);  //递归调用
        chosen.pop_back(); //恢复现场
    }
}

void Subset(int n)
{
    vector<int> chosen;  //不用特意处理【空集】
    Subset(n, 1, chosen);
}

枚举组合

【组合】其实就是固定数目的【子集】,所以算法和【枚举子集】基本一致,控制子集的数目就可以了。

(P(n,m,j)) 表示 “从第j个元素开始” 的所有的元素的【子集】,【子集】的数目必须为m:

[P(n,m,j) = {j} cup P(n,m-1,j+1);igcup ; P(n,m,j+1) ag{1}]

//n:集合个数,m:【还需要选几个】 begin: 从第几个开始选择 chosen:返回的答案
void Combination(int n, int m, int begin, vector<int> &chosen)
{
    if ((n - begin + 1) < m) //剩下的不够选了,剪枝了
        return;
   //选择begin
    chosen.push_back(begin);  
    if (chosen.size() == m)
        DoSomething(chosen);
    else
        Combination(n, m - 1, begin + 1, chosen);
    chosen.pop_back();  //恢复现场,因为chosen是【引用】
    //不选择
    Combination(n, m, begin + 1, chosen);
}

void Combination(int n, int m)
{
    vector<int> chosen;
    DoSomething(chosen);  //注意:处理【空集】
    Combination(n, m, 1, chosen);
}

也可以如下定义:
[P(n,m,j) = igcup_{k=j}^n ig( {j} cup P(n,m-1,k+1) ig) ]

//n:集合个数,m:【还需要选几个】 begin: 从第几个开始选择 chosen:返回的答案
void Combination(int n, int m, int begin, vector<int> &chosen)
{
    if ((n - begin + 1) < m)
        return;
    if (chosen.size() == m)
    {
        DoSomething(chosen);
        return;
    }

    for (int i = begin; i <= n; i++)
    {
        chosen.push_back(i);
        Combination(n, m - 1, i + 1, chosen);
        chosen.pop_back();
    }
}

void Combination(int n, int m)
{
    vector<int> chosen;
    Combination(n, m, 1, chosen);
}








以上是关于二进制集合运算的主要内容,如果未能解决你的问题,请参考以下文章

2020超全python中常用的运算符集合_逻辑教育

集合枚举

基础算法集合

代码片段 - Golang 实现集合操作

laravel特殊功能代码片段集合

python 全栈 python基础 三元运算 字符编码 元组 集合 三级菜单优化!