SOS DP 学习笔记
0.0 前言
本文大部分译自 CF 博客上的原文。Link here
0.1 前置知识
- 状压 DP
1.0 简介
SOS DP,全称 Sum over Subsets dynamic programming,意为子集和 DP,用来解决一些涉及子集和计算的问题。
1.1 例题引入
给定一个含 2 N 2^N 2N 个整数的集合 A,我们需要计算:对于每个集合 x ∈ A x\in A x∈A,求 x x x 中所有元素 i i i 的 A [ i ] A[i] A[i] 的和,即求:
F [ s t a ] = ∑ i ⊆ s t a A [ i ] {F[sta]=\sum\limits_{i\subseteq sta} A[i]} F[sta]=i⊆sta∑A[i]
1.2 解决方案
1.2.1 朴素算法
直接按照题意模拟即可,复杂度为 O ( 4 N ) O(4^N) O(4N)。
for(int sta=0;sta<(1<<N);sta++)
for(int i=0;i<(1<<N);i++)
if((sta&i)==i)F[sta]+=A[i];
1.2.2 枚举子集
对于每个状态,我们只遍历它的子集而去除了无关状态。如果一个状态的二进制位上只有 k k k 个 1 1 1,我们只需枚举它的 2 k 2^k 2k 个子集。这样的状态一共有 ( N k ) \dbinom{N}{k} (kN) 个,因此总迭代次数 = ∑ k = 0 N ( N k ) 2 k = ( 1 + 2 ) N = 3 N =\sum\limits_{k=0}^N{\dbinom{N}{k}2^k}=(1+2)^N=3^N =k=0∑N(kN)2k=(1+2)N=3N,时间复杂度即为 O ( 3 N ) O(3^N) O(3N)。
for(int sta=0;sta<(1<<N);sta++)
{
F[sta]=A[0];
for(int i=sta;i>0;i=(i-1)&sta)
F[sta]+=A[i];
}
1.2.3 SOS DP
上面枚举子集的方法有明显的缺陷:当一个状态的二进制位上有 k k k 个 0 0 0 时,它将在其他(不包含本身)状态迭代时被访问 2 k − 1 2^k-1 2k−1 次,存在重复的计算。
而产生这种现象的原因就是:我们没有在 A [ x ] A[x] A[x] 被不同 F [ s t a ] F[sta] F[sta] 利用时建立一定的联系。我们应添加另一个状态来避免上述的重复计算。
定义状态 S ( s t a ) = { x ∣ x ⊆ s t a } S(sta)=\{x|x\subseteq sta\} S(sta)={ x∣x⊆sta}。现在我们把这个集合划分为不相交的组。
S ( s t a , i ) = { x ∣ x ⊆ s t a & & s t a ⊕ x < 2 i + 1 } S(sta,i)=\{x|x\subseteq sta \&\& sta\oplus x<2^{i+1}\} S(sta,i)={ x∣x⊆sta&&sta⊕x<2i+1}。我们将二进制位数从 0 0 0 开始从低位向高位表示,那集合 S ( s t a , i ) S(sta,i) S(sta,i