子集枚举

子集遍历

for (int i = 0; i < 1 << n; i ++ )

这个代码可以从 \(0\) 开始不重复的遍历每一个二进制数,因此时间复杂度 \(O(2^n)\)

子集枚举

有的时候,我们希望枚举集合 \(i\) 所对应的所有自己,显然我们不可能循环套循环求与的方法做,时间复杂度是 \(O(2^{2n})\) 的,不优。

我们下面介绍子集枚举,他可以按照降序排序枚举集合。

for (int S = 0; S < 1 << n; i ++ )
	for (int T = S; T; T = (T - 1) & S)

\(T\) 最初恰好是集合 \(S\),在下一次循环时,我们让 \(T = (T - 1) \& S\),容易发现,把 \(T\) 的最后一个位掩码去掉了。

S = 1010100
T = 1010100 -> T = 1010011 & 1010100 = 1010000

再进行下一次循环,我们把刚才的比特恢复,消去下一个比特。

S = 1010100
T = 1010000 -> T = 1001111 & 1010100 = 1000100

接着再移去最后一个比特。

S = 1010100
T = 1000100 -> T = 1000011 & 1010100 = 1000000

以此类推,不难发现,T = (T - 1) & S 等价于我们倒序枚举连续集合的 T -- ,相当于无视了中间的 \(0\) 进行转移,这是优秀的,保证了每个子集唯一出现一次。

考虑每个枚举集合 \(S\) 的子集 \(T\) 的次数:

\[\begin{aligned}\sum_{S \subseteq \{1, \, 2, \, \cdots, \, n\}}\sum_{T \subseteq S}1 &= \sum_{i = 0}^n\binom{n}{i}2^i \\ &= (2 + 1)^n \\ &= 3^n\end{aligned} \]

因此子集枚举的复杂度是 \(O(3^n)\)

相关题目

这个东西通常用在一些 状压DP 的状态转移中,具体可以参考一下下面的题目。

最小斯坦纳树

posted @ 2025-03-30 01:08  YipChip  阅读(38)  评论(0)    收藏  举报