SoSdp 学习笔记
SoSdp 用来解决这种问题:
对于非负整数 \(i\),\(K\),定义布尔型二元运算 \(i\subseteq K\),可以以下四种等价角度理解:
- \(i \operatorname{bitand} K = i\)。\(\operatorname{bitand}\) 是按位与的意思。
- 同一个二进制位上,\(i\) 的这一位小于等于 \(K\) 的这一位。
- 同一个二进制位上,\(K\) 这一位为 \(1\),则 \(i\) 这一位可以为 \(1\) 或 \(0\);\(K\) 这一位为 \(0\),则 \(i\) 这一位只能为 \(0\)。
- \(i\) 的所有为 \(1\) 的二进制位数集,是 \(K\) 的所有为 \(1\) 的二进制位数集的子集。
设
\(a\) 是一个长度为 \(2^N\) 的数组,要求对于所有 \(0 \le K < 2^N\),算出 \(f(K)\)。上面的 \(\sum\) 可以换成其它合并运算。
方法一
对每个 \(K\),暴力计算 \(f(K)\)。对每个 \(f(K)\),暴力枚举 \(i\),\(\Theta(1)\) 判断是否 \(i \subseteq K\),累计 \(f(K)\) 的答案。
时间复杂度 \(\Theta(4^N)\)。
方法二
事实上,有办法只枚举一个数的二进制子集而不枚举其他数,方法比较简单:
初始令 \(i = K\),每次令 \(i \gets (i - 1) \operatorname{bitand} K\),直到 \(i = 0\) 为止。期间访问到的 \(i\) 都是 \(K\) 的子集。
仍对每个 \(K\) 暴力计算 \(f(K)\),但对每个 \(f(K)\),采用上述方法只枚举其子集。
考虑 \(K\) 的二进制子集数量,不难发现是 \(2^{\mathrm{popcount}(K)}\)。可以证明 \(\sum \limits_{K = 0}^{2^N - 1} 2^{\operatorname{popcount}(K)} = 3^N\)。
因此上述算法的时间复杂度是 \(\Theta(3^N)\)。
方法三
是否还可以优化?观察到每个 \(f(K)\) 都是由不超过 \(2^N\) 种 \(a_i\) 构成,每个 \(a_i\) 只作为一个小单位累积到 \(f(K)\) 还是过于暴力了。
考虑一个边长为 \(2\) 的 \(N\) 维超立方体,即一个 \(N\) 维数组 a[2][2][2]...[2][2]
。
然后把一个数 \(K\) 写成二进制形式,然后填入上面的下标,比如 \(K = (110)_2\),\(N = 4\),就可以记作 a[0][1][1][0]
。
考虑前缀和的定义:a
的前缀和数组 sum
中,有sum[K1][K2][K3]..[Kn]
等于所有满足以下条件的 a[k1][k2][k3]...[kn]
的和:\(k_1 \le K_1\),\(k_2 \le K_2\),\(k_3 \le K_3\),……,\(k_n \le K_n\)。
不难发现上面这个条件和二进制子集的某个定义是非常类似的:同一个二进制位上,\(i\) 的这一位小于等于 \(K\) 的这一位。
因此,满足上面这个条件的和,即高维前缀和,与二进制子集的和也是类似的,我们可以类比着做。具体可以看代码。
高维前缀和的复杂度可以做到 \(\Theta(N \times 2^N)\)。本做法的复杂度也可以。
注意:维数循环在外层,当前状态循环在里层。