码迷,mamicode.com
首页 > 其他好文 > 详细

二进制集合运算

时间:2018-07-20 14:36:39      阅读:411      评论:0      收藏:0      [点我收藏+]

标签:sim   pre   display   box   i++   header   .com   增加   spl   

二进制集合运算


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

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

\[ \{ 2,4,5 \} \rightarrow 110\overbrace{\fbox{1}}^{包含 2 }0\underbrace{ \fbox{0}}_{不包含0} \tag{1}\]

集合的表示

集合 c++ 代码 备注
\(E\) :全集 $(1<< n) -1 $ $ 2^n -1 = \underbrace{(111\dots111)}_{共n位} $
\(\{k\}:第k个元素\) $ 1 << k$ \(00\dots\overbrace{\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_{k\in S} 1 = \sum_{k=0}^{n-1}[k\in 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) = \bigcup_{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)\;\bigcup \; 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) = \bigcup_{k=j}^n \big( \{j\} \cup P(S,k+1) \big) \]

其含义是:从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)\;\bigcup \; P(n,m,j+1) \tag{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) = \bigcup_{k=j}^n \big( \{j\} \cup P(n,m-1,k+1) \big) \]

//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);
}

二进制集合运算

标签:sim   pre   display   box   i++   header   .com   增加   spl   

原文地址:https://www.cnblogs.com/headchen/p/9341210.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!