子集枚举
子集枚举
给定一个由 $n$ 个不同的数构成的集合,求出这个集合所有的子集。
容易知道的是,对于每个数来说,有选或不选两种情况,故一共有 $2^n$ 个子集。
如此,这个 $n$ 一定不能太大,否则我们没办法枚举出所有的子集。
常见的方式是,用一个长度为 $n$ 的全 $1$ 的二进制数表示一个集合中的所有的数。
考虑为什么不能按 lowbit 来做,其实是可以的,但是其实指令次数更多,因为你要加,你要减
对于一个数 $m$,其二进制位中最低的 $1$ 为 $k$。
令 $s = m$,
$s-1$ 就是将 $[0,k-1]$ 这 $k$ 位设置为 $1$,$k$ 这位设置为 $0$,其余位仍为 $s$ 中对应的位。
然后 (s-1) & m
就是将 $s$ 中最低位 $k$ 删除,由于二进制的性质,再加上原本比 $k$ 更低的一些为 $1$ 的位即是最新的子集。
|
|
时间复杂度分析
由于遍历只会枚举每个子集,二进制中 $1$ 的数量为 $count$ ,则时间复杂度为 $O(2^{count})$
拓展1
对于一个至多有 $n$ 个数的集合 $set$,考虑这个集合的每个子集,会作为多少其他集合的子集?
对应一个子集 $subset$,选择集合 $set$ 中不为子集的 $subset$ 的子集 $sb$,即 $sb \cup subset=set, sb\cap subset = \emptyset$。
那么枚举 $sb$ 的所有子集,与 $subset$ 取并后的结果就是 包含 $subset$ 的所有 $set$ 的子集。
时间复杂度仍为指数级
拓展2
遍历所有掩码的子掩码
|
|
时间复杂度分析,对于每位有三种情况,
- 如果掩码该位为0,则子掩码该位为0
- 如果掩码该位为1,则子掩码该位可能为0,可能为1
故一共每位有三种情况,总共是 $O(3^n)$ 的复杂度。
另一种分析方式为,对于每个有 $k$ 个二进制位的掩码,一共有 $2^k$ 个子掩码,而一共有 $C(n,k)$ 个掩码的二进制位 $1$ 个数为 $k$。故总和为:$\sum\limits_{i=1}^nC_n^k 2^k$,根据二项式定理 $(a+b)^n=\sum\limits_{k=1}^n C_n^k a^k b^{n-k}$,令 $a=2,b=1$,总和为 $O(3^n)$