SOS dp:Sum over Subsets dp ,子集和 dp
0.定义:
-
子集:对于两个二进制数 x , y x,y x,y,
x ⊆ y ⟺ x ∣ y = y x \sube y \iff x \mid y=y x⊆y⟺x∣y=y。
如 1001 1001 1001 就是 1011 1011 1011 的子集。
1.问题:
给定
2
n
2^n
2n 长度的序列
A
A
A,下标从
0
0
0 到
2
n
−
1
2^n-1
2n−1。
∀
x
\forall x
∀x,求
F
(
x
)
=
∑
i
⊆
x
A
i
F(x)=\sum\limits_{i \sube x} A_i
F(x)=i⊆x∑Ai。
2. 暴力做法
for(int mask=0; mask<(1<<N); mask++)
for(int i=0; i<(1<<N); i++)
if((mask | i) == mask) F[mask] += A[i];
时间复杂度 O ( 4 n ) O(4^n) O(4n)。
3. 子集枚举法
枚举 S S S 及其子集 x x x
for(int mask=1; mask<(1<<N); mask++)
for(int x=mask; x>0; x=(x-1)&mask)
F[mask] += A[x];
}
时间复杂度 O ( 3 n ) O(3^n) O(3n)。
O ( 3 n ) O(3^n) O(3n) 的解释:对于所有枚举的状态,对于第 i i i 个元素而言,有三种情况:属于 x x x,属于 m a s k mask mask 不属于 x x x,不属于 m a s k mask mask,因此共 3 n 3^n 3n 种状态。
4. SoS DP 解法
我们发现在上述方法中,有一个明显的缺陷:对于一个有 k k k 位 0 0 0 的二进制数 x x x,它是 2 k 2^k 2k 个集合的子集,因此 A [ x ] A[x] A[x] 会被重复计算 2 k 2^k 2k 次。我们考虑一种新的集合划分方式使得 A [ x ] A[x] A[x] 在新的集合中尽量不被重复划分。
记 S ( m a s k , i ) = { x ∣ x ⊆ m a s k & & x ⊕ m a s k < 2 i + 1 } S(mask,i)=\lbrace {x \mid x \sube mask \,\,\&\&\,\, x \oplus mask < 2^{i+1}} \rbrace S(mask,i)={x∣x⊆mask&&x⊕mask<2i+1} ,也就意味着 S ( m a s k , i ) S(mask,i) S(mask,i) 中的元素只会在前 i i i 位与 m a s k mask mask 不同。
例如, S ( 110101 , 2 ) = { 110 100 , 110 001 , 110 101 , 110 000 } S(110101,2)=\lbrace {110\textcolor{red}{100}, 110\textcolor{red}{001}, 110\textcolor{red}{101}, 110\textcolor{red}{000}} \rbrace S(110101,2)={110100,110001,110101,110000}。
我们尝试去找 S S S 集合之间的关系:
- 当 m a s k mask mask 第 i i i 位为 0 0 0 时, S ( m a s k , i ) = S ( m a s k , i − 1 ) S(mask,i)=S(mask,i-1) S(mask,i)=S(mask,i−1)。
- 当 m a s k mask mask 第 i i i 位为 1 1 1 时, S ( m a s k , i ) = S ( m a s k , i − 1 ) ∪ S ( m a s k ⊕ 2 i , i − 1 ) S(mask,i)=S(mask,i-1) \cup S(mask \oplus 2^i,i-1) S(mask,i)=S(mask,i−1)∪S(mask⊕2i,i−1)。
记 d p ( m a s k , i ) = ∑ x ∈ S ( m a s k , i ) A [ x ] dp(mask,i)=\sum\limits_{x \in S(mask,i)} {A[x]} dp(mask,i)=x∈S(mask,i)∑A[x],有:
d p ( m a s k , i ) = { d p ( m a s k , i − 1 ) i t h b i t o f f d p ( m a s k , i − 1 ) + d p ( m a s k ⊕ 2 i , i − 1 ) i t h b i t o n dp(mask, i)=\begin{cases} dp(mask, i-1) & i^{th} \,\, bit \,\, off\\ dp(mask, i-1) + dp(mask \oplus 2^i,i-1)& i^{th} \,\, bit \,\, on\\ \end{cases} dp(mask,i)={dp(mask,i−1)dp(mask,i−1)+dp(mask⊕2i,i−1)ithbitoffithbiton
d p ( m a s k , − 1 ) dp(mask,-1) dp(mask,−1) 初始为 A [ m a s k ] A[mask] A[mask]。
对于 F [ m a s k ] F[mask] F[mask],即为 d p ( m a s k , n − 1 ) dp(mask,n-1) dp(mask,n−1)。
for(int mask = 0; mask < (1<<N); mask ++)
{
dp[mask] = buf[mask] + 1;
dp[mask][-1] = a[mask];
for(int i=0; i<N; i++)
{
if(mask & (1<<i))
dp[mask][i] = dp[mask][i-1] + dp[mask^(1<<i)][i-1];
else
dp[mask][i] = dp[mask][i-1];
}
F[mask] = dp[mask][N];
}
时间复杂度 O ( n × 2 n ) O(n \times 2^n) O(n×2n)。
空间优化:
for(int mask = 0; mask < (1<<N); mask ++)
F[mask] = a[mask];
for(int i=0; i<N; i++)
for(int mask = 0; mask < (1<<N); mask ++)
if(mask & (1<<i)) F[mask] += F[mask^(1<<i)];
例题1:iai 互补数对
若 a & b = 0 a \And b=0 a&b=0 , 那么 b b b 一定是 a a a 按位取反的子集,SoS dp 后按补集统计即可。
#include<bits/stdc++.h>
using namespace std;
int n, num[1000005], F[1<<20];
signed main()
{
cin >> n;
for(int i=1; i<=n; i++)
{
cin >> num[i];
F[num[i]] ++ ;
}
for(int i=0; i<20; i++)
for(int mask = 0; mask < (1<<20); mask ++)
if(mask & (1<<i)) F[mask] += F[mask^(1<<i)];
long long ans = 0;
int ALL = (1<<20) - 1;
for(int i=1; i<=n; i++)
ans += F[ALL - num[i]];
cout << ans / 2;
return 0;
}
例题2:Compatible Numbers
例题 1 求数量,此题求具体的数,思路与方法已知。
#include<bits/stdc++.h>
using namespace std;
int n, num[1000005], g[1<<22];
signed main()
{
cin >> n;
for(int mask = 0; mask < (1<<22); mask ++) g[mask] = -1;
for(int i=1; i<=n; i++)
{
cin >> num[i];
g[num[i]] = num[i];
}
for(int i=0; i<22; i++)
for(int mask = 0; mask < (1<<22); mask ++)
if(mask & (1<<i) and g[mask] == -1) g[mask] = g[mask^(1<<i)];
int ALL = (1<<22) - 1;
for(int i=1; i<=n; i++)
cout << g[ALL-num[i]] << " ";
return 0;
}
例题3:Jzzhu and Numbers
非常棒的一道容斥计数题。
例题4:KOŠARE
m ≤ 20 m \le 20 m≤20,很容易想到将箱子对应的玩具种类状态压缩。
题目即求每一位上都至少有一个 1 1 1 的方案数,若将状态取反,即转换为例题 3。
例题5:Vowels
首先,我们不关心每个字母出现了几次,只关心是否出现,考虑用二进制状压表示是否出现。
对于给定的元音集合,从反面考虑,如果一个单词不是正确的,那么他在所有的元音集合为
1
1
1 的位上均为
0
0
0。那么这个单词一定是元音集合取反后的子集。
记录1
也可以对这个单词取反,如果这个单词取反后,元音集合是其子集,那么这个单词也一定是不正确的。具体统计方式与例题 3 相同。
记录2
由于最终统计答案时要枚举所有的集合,我们同样可以直接枚举元音集合的补集。
记录3
例题6:Or Plus Max
只考虑 i ∣ j = K i \mid j = K i∣j=K 的方案数,维护 1 1 1 到 K K K 中的最大值即为 ≤ K \le K ≤K 的答案。
而 i ∣ j = K i \mid j = K i∣j=K 说明 i , j i,j i,j 都是 K K K 的子集,将一般的 SoS dp 改为求最大值和次大值即可。
正确性:可以从 O ( 3 n ) O(3^n) O(3n) 枚举子集的方法角度考虑,正确性显然,SoS dp 实际上就是对其的一种优化。