Bootstrap

SOS DP

SOSdp 简介 (by zzuzxy)

翻译自:
codeforces sos dp
先学知识:状压dp

SOSdp

全称是 S u m   o v e r   S u b s e t s   d y n a m i c   p r o g r a m m i n g Sum\ over\ Subsets\ dynamic\ programming Sum over Subsets dynamic programming, 意思就是子集和dp,其实在我看来就是状压dp的一种

从一个例题引出 S O S d p SOSdp SOSdp
F [ m a s k ] = ∑ i ∈ m a s k A [ i ] F[mask] =\sum_{i \in mask}A[i] F[mask]=imaskA[i]
我们说 i ∈ m a s k i \in mask imask即为 i & m a s k = i i \& mask = i i&mask=i, 回忆状压 d p dp dp,这应该很好理解
然后你发现,咦不太对,这个和 F W T FWT FWT好像, i & m a s k = i 和 i ∣ m a s k = m a s k i\&mask = i 和 i|mask=mask i&mask=iimask=mask是一回儿事
所以破案了, F W T = S O S d p FWT = SOS dp FWT=SOSdp
FWT
方法1, O ( 4 n ) O(4^n) O(4n)暴力枚举

for(int mask =0;mask < (1<<N); ++i){
		for(int i = 0;i < (1<<N); ++i)
			if(i&mask == i)
				F[mask] += A[i];
	}   

方法2, O ( 3 n ) O(3^n) O(3n) 枚举所有子集

for(int mask = 0;mask < (1<<N); ++mask){
		F[mask] = A[0];
		for(int i = mask; i > 0; i = (i-1)&mask)
			F[mask] += A[i];
	} 

方法3,SOS dp O ( n ∗ l o g ( n ) ) O(n*log(n)) O(nlog(n))

d p [ m a s k ] [ i ] dp[mask][i] dp[mask][i] 代表 x & m a s k = x , x ∧ m a s k < 2 i + 1 x\&mask = x,x^{\land} mask < 2^{i+1} x&mask=x,xmask<2i+1 A [ x ] A[x] A[x]的和, 意思就是 d p [ m a s k ] [ i ] dp[mask][i] dp[mask][i]是和 m a s k mask mask 只有前 i i i个位不同的 A [ x ] A[x] A[x]的和
d p [ m a s k ] [ i ] = d p [ m a s k ] [ i − 1 ] m a s k & ( 2 i ) = 0 d p [ m a s k ] [ i − 1 ] + d p [ m a s k ⊕ ( 2 i ) ] [ i − 1 ] m a s k & ( 2 i ) = 2 i dp[mask][i] = \begin {matrix} dp[mask][i-1]&mask\&(2^i)=0\\ dp[mask][i-1]+dp[mask\oplus(2^i)][i-1]&mask\&(2^i) = 2^i \end{matrix} dp[mask][i]=dp[mask][i1]dp[mask][i1]+dp[mask(2i)][i1]mask&(2i)=0mask&(2i)=2i

如下图

在这里插入图片描述

//iterative version
for(int mask = 0; mask < (1<<N); ++mask){
	dp[mask][-1] = A[mask];	//handle base case separately (leaf states)
	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-1];
}

//memory optimized, super easy to code.
for(int i = 0; i<(1<<N); ++i)
	F[i] = A[i];
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. CF1208F
    题意:求 m a x ( a i ∣ ( a j & a k ) ) , 1 ≤ i < j < k ≤ n max(a_i |(a_j\&a_k)),1\leq i<j<k\leq n max(ai(aj&ak)),1i<j<kn
    分析: 从后往前依次将每一个数加入 S O S d p SOSdp SOSdp中,记忆化一下
    参考代码:code

  2. SPECIAL PAIRS
    题意: 求 a i & a j = 0 a_i\&a_j=0 ai&aj=0 ( i , j ) (i,j) (i,j)的个数
    分析:sos dp 即可
    参考代码: code1-FWT code2-sosdp

  3. E. Compatible Numbers
    题意: 给定 a 1 , . . a n a_1,..a_n a1,..an求对于每一个 a i a_i ai是否存在 a j a_j aj满足 a i & a j = 0 a_i\&a_j=0 ai&aj=0
    分析:同上一题一样,只需要记录一下转移从何处而来即可

  4. E - Vowels
    题意:字符集大小为24,有n个单词,每个单词有三个字母,如果其中有一个元音,我们就称这个单词是合法的,元音的集合有 2 24 2^{24} 224种可能,求这 2 24 2^{24} 224种元音集合的正确单词数的平方的异或和。
    分析:现在的问题变成了 a i & m a s k ! = 0 a_i\&mask != 0 ai&mask!=0,我们注意到字母的个数很小,那么我们就可以使用容斥定理,求出一个字母的,两个字母,三个字母的贡献,然后进行 S O S d p SOSdp SOSdp即可
    参考代码:code

  5. Covering Sets CodeChef - COVERING
    题意: 给定集合 A , B , C A,B,C A,B,C,定义运算 A ( i ) , B ( i ) , C ( i ) A(i),B(i),C(i) A(i),B(i),C(i),返回集合的第i个值(从0开始), 定义一个三元组 ( A ( i ) , B ( j ) , C ( k ) ) (\textbf A(i),\textbf B(j),\textbf C(k)) (A(i),B(j),C(k)) 的并集为 i ∣ j ∣ k i|j|k ijk,如果 l & ( i ∣ j ∣ k ) = l l \&(i|j|k) = l l&(ijk)=l,那么我们就称 ( A ( i ) , B ( j ) , C ( k ) ) (\textbf A(i),\textbf B(j),\textbf C(k)) (A(i),B(j),C(k)) 覆盖 l l l R ( l ) R(l) R(l)定义所有的覆盖 l的三元组的乘积的和即
    R ( l ) = ∑ l & ( i ∣ j ∣ k ) = l A ( i ) ∗ B ( j ) ∗ C ( k ) R(l) = \sum_{l\&(i|j|k)=l} A(i)*B(j)*C(k) R(l)=l&(ijk)=lA(i)B(j)C(k)
    R ( 0 ) + R ( 1 ) + R ( 2 ) + . . . R ( 2 N − 1 ) R(0)+R(1)+R(2)+...R(2^N-1) R(0)+R(1)+R(2)+...R(2N1)
    分析:直接枚举 l l l求满足的集合是不行的,我们可以统计每一个三元组 ( A ( i ) , B ( j ) , C ( k ) ) (\textbf A(i),\textbf B(j),\textbf C(k)) (A(i),B(j),C(k))的贡献,我们发现它能够对 t = 2 d ( i ∣ j ∣ k ) t = 2^{d (i|j|k)} t=2d(ijk) l l l有贡献 ,所以我们统计所有的三元组,这样的复杂度过大,但是如果 i ∣ j ∣ k i|j|k ijk相等的集合统一算贡献就可以了,对 A , B , C A,B,C ABC都计算一次 S O S d p SOSdp SOSdp, 然后我们就计算出了相乘,但是这样会有重复,我们需要减去 i ∣ j ∣ k i|j|k ijk的真子集,把 S O S d p SOSdp SOSdp倒着做一次即可
    参考代码code

  6. D. Jzzhu and Numbers给定 a 1 , . . a n a_1,..a_n a1,..an求有多少集合按位与的值为0
    参考代码code1

  7. COCI 2011/2012 Problem KOSARE给定 a 1 , . . . n a_1,..._n a1,...n 求有多少个集合按位或为全集
    以上两题其实可以看做是一个题,先做一遍 S O S d p SOSdp SOSdp求出 F [ m a s k ] F[mask] F[mask],然后容斥
    参考代码code2

  8. hackrank subsets
    题意
    操作1:在集合中插入一个元素
    操作2:在集合中删除一个元素
    操作3:查询集合中有多少个元素满足 a & s = a a\&s =a a&s=a
    分析:
    我们用 b i t ( a ) bit(a) bit(a)表示 a a a的二进制表示1的数量
    方法1:修改枚举前8位,查询枚举后八位,子集枚举复杂度 O ( Q ∗ 8 ∗ 2 8 ) O(Q*8*2^8) O(Q828)
    d p [ i ] [ j ] dp[i][j] dp[i][j] 代表 l o w 8 & i = l o w 8 , h i g h 8 = j low_8 \&i=low_8,high_8 = j low8&i=low8,high8=j的a个数
    修改的时候枚举包含前八位的集合
    查询的时候,枚举后八位的子集,前8位的子集就是 d p [ l o w 8 ] dp[low_8] dp[low8]
    方法2:维护3个数组 F , D , E F,D,E F,D,E
    F: 维护的是 a a a b i t ( a ) > 8 ) bit(a)>8) bit(a)>8) F [ i ] F[i] F[i]就是通常的 F [ m a s k ] F[mask] F[mask],即 m a s k & a = a mask\&a =a mask&a=a
    D:维护的是 a a a b i t ( a ) ≤ 8 bit(a)\leq 8 bit(a)8, D [ i ] D[i] D[i] a = i a=i a=i的a的数量
    E:维护的是 a a a b i t ( a ) ≤ 8 bit(a)\leq 8 bit(a)8, E [ m a s k ] E[mask] E[mask] m a s k & a = m a s k mask\& a=mask mask&a=mask的数量
    修改操作

    1. b i t ( a ) ≤ 8 bit(a) \leq 8 bit(a)8,修改 D , E D,E D,E
    2. b i t ( a ) > 8 bit(a) > 8 bit(a)>8,修改F,修改F就直接暴力修改,枚举所有包含 a & m a s k = a a\&mask = a a&mask=a的集合
      查询操作
    3. b i t ( s ) ≤ 8 bit(s) \leq 8 bit(s)8, 查询 D D D即可,枚举 a a a的所有子集
    4. b i t ( s ) > 8 bit(s) > 8 bit(s)>8 , F [ s ] F[s] F[s] b i t ( a ) > 8 bit(a) > 8 bit(a)>8的贡献,考虑怎么求 b i t ( a ) ≤ 8 bit(a) \leq 8 bit(a)8的贡献,我们对 s s s取反得到 s s ss ss,求 a & s s ! = 0 a\&ss!=0 a&ss!=0的个数,这就变成了第4题
      参考代码code 1code2
  9. Jersey Number
    题意:给定一个字符串,求有多少对区间有至少一个相同的字符
    分析:(icpc上这道题不能提交,数据是错的)先处理出来所有的区间的值,然后进行 S O S d p SOSdp SOSdp即可
    参考代码code

  10. Beautiful Sandwich
    题意:给定一个字符串,字符集不超过18个,定义一个字符串的value 为 连续相同字符个数的平方,求每次删除几种字符后字符串的价值。
    分析:仔细想想其实和上一题有相似之处,平方我们可以拆开, A ∗ A = ( 1 + . . . 1 ) A 个 1 ∗ ( 1 + . . . 1 ) A 个 1 A*A = {(1+...1)}_{A个1}*{(1+...1)}_{A个1} AA=(1+...1)A1(1+...1)A1,1代表一个字符出现一次,例如 a b a c a a abacaa abacaa,我们考虑第一个a的贡献,从位置1开始,每次加入一个新的 a a a,如果删除 b b b,那么增加的值是 1 ( 第 一 个 a ) ∗ 1 ( 第 二 个 a ) ∗ 2 1(第一个a)*1(第二个a)*2 1(a)1(a)2,如果删除 b c bc bc,那么贡献就又增加了 1 ∗ 2 ( 第 三 个 , 第 四 个 a ) ∗ 2 1*2(第三个,第四个a)*2 12(a)2,这样就把贡献拆开,利用 S O S d p SOSdp SOSdp来做
    参考代码code,注意爆int

  11. K. Pepsi Cola
    题意: 求 2 N 2^N 2N子集合所有元素或的三次方
    分析:需要求出来每个值有多少个集合,采用 S O S d p SOSdp SOSdp,需要搞懂第5题

  12. Uchiha Brothers and Two Products

    题意:物品有两个属性,价值 p i p_i pi和营养价值 a i a_i ai,定义一个集合的营养价值为 & a i ( i ∈ S ) \&a_i(i\in S) &ai(iS),定义一个集合的价值为 ∏ i ∈ S p i \prod_{i\in S} p_i iSpi,给定营养价值 s s s,取所有营养价值相同为s的集合的价值的和.
    分析:会发现营养价值这个条件就是 S O S d p SOSdp SOSdp的内容,怎么将所有营养价值的和变成乘积,最后发现这个其实也是dp,比方说加入一个价值为b的 d p [ i ] = d p [ i ] + d p [ i ] ∗ b + b dp[i] = dp[i]+dp[i]*b+b dp[i]=dp[i]+dp[i]b+b.将原本sosdp的内容修改即可,然后我们求出来 F [ m a s k ] F[mask] F[mask],依然要做一次反 S O S d p SOSdp SOSdp

    参考代码code

  13. Strange Functions
    题意:给定 A [ i ] A[i] A[i],已知 F [ i ] = A [ i ] 2 + ∑ ( i & j ) = j   a n d   j < i G [ j ] ) 2 G [ i ] = ∑ ( i & j ) = j   a n d   j ≤ i ( F [ j ] ) 2 \quad \quad F[i] = {A[i]}^2 +\sum_{(i\&j) = j\ and\ j < i} G[j])^2 \\ \quad\\ G[i] = ∑_{ (i\&j) = j\ and\ j ≤ i} {(F[j])}^2 F[i]=A[i]2+(i&j)=j and j<iG[j])2G[i]=(i&j)=j and ji(F[j])2
    分析: 老老实实按照 d p [ m a s k ] [ i ] dp[mask][i] dp[mask][i]来维护 ∑ G [ i ] \sum{G[i]} G[i] ∑ F [ i ] 2 \sum{F[i]}^2 F[i]2
    参考代码: code

  14. D. Varying Kibibits
    题意:题意有些复杂,建议自己理解
    分析:怎么维护平方呢?不知道有没有做过线段树维护区间和平方那个题,就是维护一次项和二次项,怎么维护集合的平方呢?考虑再维护集合的个数即可,最后做一遍反 SOSdp即可
    参考代码:code

;