浅谈子集和 dp SoS dp

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 xyxy=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 2n1
∀ x \forall x x,求 F ( x ) = ∑ i ⊆ x A i F(x)=\sum\limits_{i \sube x} A_i F(x)=ixAi

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)={xxmask&&xmask<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,i1)
  • 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,i1)S(mask2i,i1)

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)=xS(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,i1)dp(mask,i1)+dp(mask2i,i1)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,n1)

	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 互补数对

link

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

link

例题 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

link

非常棒的一道容斥计数题。

题解

例题4:KOŠARE

link

m ≤ 20 m \le 20 m20,很容易想到将箱子对应的玩具种类状态压缩。

题目即求每一位上都至少有一个 1 1 1 的方案数,若将状态取反,即转换为例题 3。

例题5:Vowels

link

首先,我们不关心每个字母出现了几次,只关心是否出现,考虑用二进制状压表示是否出现。

对于给定的元音集合,从反面考虑,如果一个单词不是正确的,那么他在所有的元音集合为 1 1 1 的位上均为 0 0 0。那么这个单词一定是元音集合取反后的子集。
记录1

也可以对这个单词取反,如果这个单词取反后,元音集合是其子集,那么这个单词也一定是不正确的。具体统计方式与例题 3 相同。
记录2

由于最终统计答案时要枚举所有的集合,我们同样可以直接枚举元音集合的补集。
记录3

例题6:Or Plus Max

只考虑 i ∣ j = K i \mid j = K ij=K 的方案数,维护 1 1 1 K K K 中的最大值即为 ≤ K \le K K 的答案。

i ∣ j = K i \mid j = K ij=K 说明 i , j i,j i,j 都是 K K K 的子集,将一般的 SoS dp 改为求最大值和次大值即可。

正确性:可以从 O ( 3 n ) O(3^n) O(3n) 枚举子集的方法角度考虑,正确性显然,SoS dp 实际上就是对其的一种优化。

记录

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值