从0到n每个数二进制枚举子集的总复杂度

384 阅读2分钟

leetcode 第 218 场周赛第四题,坑了我许久。看到许多解答是用状态压缩(具体而言,二进制子集枚举)DP 来做的,典型如 大佬z zerotrac 的题解。当中提到:

  1. 对于位数不超过 nn 的二进制数,枚举它的所有子集的时间复杂度为 O(2n)O(2^n)
  2. 然而如果我们枚举每一个不超过 nn 的二进制数的所有子集,(即,0 枚举子集,1 枚举子集,2 枚举子集,... 2n12^n - 1 枚举子集),那么时间复杂度实际上不是 O(2n2n)=O(4n)O(2^n \cdot 2^n) = O(4^n),而是 O(3n)O(3^n),具体可以用二项式定理来证明。

题目中 nn 的最大值是 16,所以 4n=232>1094^n = 2 ^{32} > 10^9 显然会超时,如果复杂度果真如此,这个状压 DP 方法就不能用了。而如果是 3n3^n 则不会超时。所以搞清楚这里的复杂度对于确认思路是非常关键的。

随手证明一下:

考虑 0,1,2,...2n10, 1, 2, ... 2^n-1 每一个数都做子集枚举。那么,

  • 空集(00) 是每个数的子集,因此出现了 2n2^n 次。
  • 对于某个大小为 11 的子集,它会被 2n2=2n1\frac{2^n}{2} = 2^{n-1} 个数包含。原因容易理解,因为 0,1,2,...2n10, 1, 2, ... 2^n-1 实际上均匀地列出了 2n12^n-1 的各个子集,因此对于某元素来说,会有一半的子集中该存在该元素,而另一半不存在。而大小为 11 的子集共有 Cn1C_n^1 个。
  • 对于某个大小为 22 的子集,它会被 2n22=2n2\frac{2^n}{2^2} = 2^{n-2} 个数包含。而大小为 22 的子集共有 Cn2C_n^2 个。
  • ……
  • 对于大小为 nn 的子集,它会被 2n2n=20\frac{2^n}{2^n} = 2^{0} 个数包含。而大小为 nn 的子集只有一个。(对于全集,只有最后那个全集 2n12^n-1 能枚举到它。)

所以全部加起来,正好是 count=Cn02n+Cn12n1+Cn22n2++Cnn20count = C_n^0 \cdot 2^n + C_n^1 \cdot 2^{n-1} + C_n^2 \cdot 2^{n-2} + \ldots + C_n^n \cdot 2^0

自动脑补每一项乘以 11 的某些次方,结果正好是 (2+1)n(2 + 1) ^ n 的二项展开式。因此,每个数枚举子集的复杂度总和确实为 3n3^n。只要写的时候加上合适的预处理和剪枝,过题是没问题的。