SOS(Sum over Subsets)dp————一类状态压缩动态规划

之前补cf场时做到这道题,发现一点思路也没有?然后看了题解发现这是一类codeforces上考烂了的dp专题。所以花了一天时间补了一下。

codeforces上的原博客

SOSdp是一类计算子集贡献的状压dp,如果x&y==x,则我们称yx的子集(可能不太标准),例如5(101)的子集有4(100)、1 (001)、0。而我们要求的就是

F [ m a s k ] = ∑ i ⊂ m a s k A [ i ] F[mask] = \sum_{i\subset mask}A[i] F[mask]=imaskA[i]

如何求解?我们先设 d p [ m a s k ] [ i ] dp[mask][i] dp[mask][i]表示二进制从低到高(从0开始计数)已经处理完前i位的情况。例如, d p [ 1011011 ] [ 2 ] dp[1011011][2] dp[1011011][2]表示前3项011已经计算完成的状态,所以此时 d p [ 1011011 ] [ 2 ] dp[1011011][2] dp[1011011][2]代表的是 A [ 1011011 ] 、 A [ 1011010 ] 、 A [ 1011001 ] 、 A [ 1011000 ] A[1011011]、A[1011010]、A[1011001]、A[1011000] A[1011011]A[1011010]A[1011001]A[1011000]的和。
初始, d p [ m a s k ] [ − 1 ] = A [ i ] dp[mask][-1]=A[i] dp[mask][1]=A[i]
然后转移则是如果第i位为0,那么直接转移 d p [ m a s k ] [ i ] = d p [ m a s k ] [ i − 1 ] dp[mask][i]=dp[mask][i-1] dp[mask][i]=dp[mask][i1]。表示此时当前位没得选择,只能由前i-1位转移过来。如果第i位为1那么 d p [ m a s k ] [ i ] = d p [ m a s k ] [ i − 1 ] + d p [ m a s k ∧ ( 1 < < i ) ] [ i − 1 ] dp[mask][i]=dp[mask][i-1]+dp[mask \wedge(1<<i)][i-1] dp[mask][i]=dp[mask][i1]+dp[mask(1<<i)][i1]表示当前如果是位1则有1和0两种情况转移过来。
然后我们就可以写代码了。

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];
}

然后我们发现,每一个 d p [ m a s k ] [ i ] dp[mask][i] dp[mask][i]只由 d p [ l a s t m a s k ] [ i − 1 ] dp[lastmask][i-1] dp[lastmask][i1]转移过来,所以类似背包,我们可以把第二维状态给压没,然后就有下面这个很好写的代码了。

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)];
}

ps:那如果反过来,如果我现在要求

F [ m a s k ] = ∑ m a s k ⊂ i A [ i ] F[mask] = \sum_{mask\subset i}A[i] F[mask]=maskiA[i]
只需要将

F[mask] += F[mask^(1<<i)];

改成

F[mask^(1<<i)] += F[mask];

即可,因为只有1能对0进行转移,所有0计算出来就不用转移。所以顺序不影响结果。这样我们就可以做博客的套题了。

你可能感兴趣的:(dp专题)