SOS DP
又叫子集 d p dp dp
有时候会遇到这样的问题,已知数组 a a a,对每一个 m a s k mask mask求
f [ m a s k ] = ∑ x & m a s k = x a x f[mask]=\sum\limits_{x\&mask=x}a_x f[mask]=x&mask=x∑ax
你当然可以暴力枚举 m a s k mask mask和 x x x求,但是这个玩意其实可以 d p dp dp
有点类似 F W T FWT FWT
定义 f [ m a s k ] [ i ] f[mask][i] f[mask][i]表示只有二进制前 i i i位和 m a s k mask mask不同的满足条件的 ∑ a x \sum\limits a_x ∑ax
那么当 m a s k mask mask的第 i i i位为 0 0 0时
满足条件的 x x x第 i i i位也必然为 0 0 0,那么实际上就等价于 f [ m a s k ] [ i − 1 ] f[mask][i-1] f[mask][i−1]
因为如果 x x x第 i i i位和 m a s k mask mask相等,就相当于只有前 i − 1 i-1 i−1位不同而已
当 m a s k mask mask的第 i i i位为 1 1 1时
那么满足条件的 x x x第 i i i位可以为 0 0 0也可以为 1 1 1
如果 x x x第 i i i位为 1 1 1,那么实际上等价于 f [ m a s k ] [ i − 1 ] f[mask][i-1] f[mask][i−1](同上)
如果 x x x第 i i i位为 0 0 0,我们仍然构造一种方案使得能够类似上面来转移
也就是等价于 f [ m a s k ⊕ 2 i ] [ i − 1 ] f[mask\oplus 2^i][i-1] f[mask⊕2i][i−1]
解 释 \color{Red}解释 解释
现在我们要找的 x x x满足 x & m a s k = x x\&mask=x x&mask=x
现在我们暂时不考虑第 i i i位二进制是否满足
那么 f [ m a s k ⊕ 2 i ] [ i − 1 ] f[mask\oplus 2^i][i-1] f[mask⊕2i][i−1]中的所有 x x x,不考虑第 i i i位时,仍然满足 x & m a s k = x x\&mask=x x&mask=x
现在考虑第 i i i位,因为 m a s k ⊕ 2 i mask\oplus 2^i mask⊕2i的第 i i i位是 0 0 0
那么 f [ m a s k ⊕ 2 i ] f[mask\oplus 2^i] f[mask⊕2i]中满足条件的 x x x的第 i i i位都是 0 0 0
显然这样的 x x x就是我们所需要的 x x x
所以有转移方程 f [ m a s k ] [ i ] = f [ m a s k ] [ i − 1 ] + f [ m a s k ⊕ 2 i ] [ i − 1 ] f[mask][i]=f[mask][i-1]+f[mask\oplus2^i][i-1] f[mask][i]=f[mask][i−1]+f[mask⊕2i][i−1]
可以滚动数组优化空间,枚举 i i i,再枚举 m a s k mask mask即可
因为每次从 i , m a s k i,mask i,mask更小的地方转移而来,所以 m a s k mask mask应该倒序枚举保证是上一轮的状态
for(int i = 0; i<(1<<N); ++i) F[i] = A[i];
for(int i = 0;i < N; ++i)
for (int mask = (1<<N)-1; mask >= 0 ; mask--)
if (mask & (1 << i))
F[mask] += F[mask ^ (1 << i)];
但是其实 m a s k mask mask正序枚举也是 可以的…因为 m a s k ⊕ 2 i mask \oplus 2^i mask⊕2i一定是直接继承的上一次的状态,所以不会有影响
for(int mask = 0; mask < (1<<N); ++mask){
if(mask & (1<<i)) F[mask] += F[mask^(1<<i)];
事实上网上博客基本都是正序来写的…虽然这样没问题,但是不利于理解吧??..
例题
没有链接了,没有评测点
题意
给定 n n n个数,保证 n n n和数字都小于等于 1 0 6 10^6 106
求 a i & a j = = 0 a_i\&a_j==0 ai&aj==0的 ( i , j ) (i,j) (i,j)的数目. i , j i,j i,j无序,贡献算两次
我们把每个数字 x x x的数目叠起来作为 a x a_x ax,然后求
f [ m a s k ] = ∑ x & m a s k = x a x f[mask]=\sum\limits_{x\&mask=x}a_x f[mask]=x&mask=x∑ax就可以了
但是 f [ m a s k ] f[mask] f[mask]表示那些是 m a s k mask mask子集的 a x a_x ax的和
现在我们是求不是子集的和,那么也就是求 m a s k mask mask的补集的 f f f值即可
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1<<20;
int t,n,a[maxn<<1],f[maxn<<1],mx=(1<<20);
int main()
{
cin >> t;
while( t-- )
{
cin >> n;
for(int i=1;i<=n;i++)
{
int x; scanf("%d",&x);
a[x]++;
}
for(int i=0;i<mx;i++) f[i] = a[i];
for(int i=0;i<20;i++)
{
for(int j=mx-1;j>=0;j--)
if( j&(1<<i) ) f[j] += f[j^(1<<i)];
}
long long ans = 0;
for(int i=0;i<=1000000;i++) ans += 1ll*a[i]*f[(mx-1)^i],a[i]=0;
cout << ans << endl;
}
}