高维前缀和
\(~~~~\) 高维前缀和是用于解决有关子集或超集和问题的一种算法(或者说技巧?)
\(~~~~\) 形式化地,即求:
\(~~~~\) 或
\(~~~~\) 即每一个集合的子集或超集和,这里把一个数理解为状压的集合方案。
\(~~~~\) 一般来说,这样暴力做的时间复杂度为 \(\mathcal{O(3^n)}\) (枚举子集及其子集),但高维前缀和可以做到 \(\mathcal{O(n\times 2^n)}\) 。
引入
\(~~~~\) 我们回忆一下一般怎么求二维前缀和。
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++) f[i][j]=f[i-1][j]+f[i][j-1]-f[i-1][j-1]+a[i][j];
}
\(~~~~\) 这是用容斥来做的,但我们还可以依次对行和列进行前缀和,即:
for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) f[i][j]=f[i-1][j]+a[i][j];
for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) f[i][j]=f[i][j-1]+a[i][j];
\(~~~~\) 看起来好像带常数,但这是可以仅通过加入若干层枚举轻松拓展到更高维的,而容斥每加一维都并不好推。
\(~~~~\) 因此,不妨以这个思路,将集合的元素个数用维来代替,那就每次处理某一维的前缀和即可。
高维前缀和
子集
\(~~~~\) 求子集的和,那就是求高维前缀和。从低到高枚举元素(即维数),然后枚举所有 \(\in [0,2^n-1]\) 的元素,用上一层的来更新当前即可。
for(int j=0;j<n;j++)
{
for(int i=0;i<(1<<n);i++) if((i>>j)&1) f[i]+=f[i^(1<<j)]; //有当前元素的才更新
}
超集
\(~~~~\) 求超集的和,那就是求高维后缀和。从高到低枚举元素(即维数),然后倒序枚举所有 \(\in [0,2^n-1]\) 的元素,用上一层的来更新当前即可。(为什么感觉跟上面这么像)
for(int j=0;j<n;j++)
{
for(int i=(1<<n)-1;~i;i--) if(!((i>>j)&1)) f[i]+=f[i^(1<<j)];//没有当前元素才更新
}
\(~~~~\) (代码其实跟上面也很像)
\(~~~~\) 所以本质上这就是个状压,不过个人理解就直接把它定位为求子集或超集和的技巧。
例题 CF449D Jzzhu and Numbers
\(~~~~\) 其实我就是想写个题解。
题意
给出一个长度为 \(n\) 的序列 \(a_1,a_2...a_n\)。求构造出一个序列 \(i_1 < i_2 < ... < i_k(1\le{k}\le{n})\) 使得 \(a_{i_1} \text{and}\ a_{i_2} \text{and}\ \dots \text{and}\ a_{i_k}=0\) 。求方案数模 \(10^9+7\) 。
也就是从 \(\{a_i\}\) 里面选出一个非空子集使这些数按位与起来为 \(0\).
题解
\(~~~~\) 一眼发现正着不好算,考虑反过来去掉不合法的。
\(~~~~\) 再发现恰好有 \(k\) 位为 \(1\) 的也不好算,因此计算至少有 \(k\) 位为 \(1\) 的,出现至少的话显然就是容斥了。
\(~~~~\) 至少有 \(k\) 位为 \(1\) ,那我们需要计算出某个方案 \(i\) 的超集有多少个元素,即有多少出现过的数是 \(i\) 的超集,这样在选择 \(i\) 时选择这些数必定会使与和的 \(1\) 的个数至少为 \(\operatorname{Popcount(i)}\) 。统计超集直接上高维前缀和。
\(~~~~\) 最后,考虑某个 \(i\) ,若共有 \(p\) 个元素可选,显然就有 \(2^p-1\) 种选法至少有 \(\operatorname{Popcount(i)}\) 个 \(1\) (即选超集的非空子集),对于每种 \(\operatorname{Popcount(i)}\) 容斥即可。
代码
查看代码
#include <cstdio>
#include <algorithm>
#define ll long long
using namespace std;
const ll MOD=1e9+7;
ll f[2000005],g[2000005];
ll qpow(ll a,ll b)
{
ll ret=1;
while(b)
{
if(b&1) ret=ret*a%MOD;
b>>=1;a=a*a%MOD;
}
return ret;
}
ll Mul(ll a,ll b){return a*b%MOD;}
ll Add(ll a,ll b){return ((a+b)%MOD+MOD)%MOD;}
ll PopCount(ll x){ll ret=0;while(x){ret+=x&1;x>>=1;}return ret;}
int main() {
int n;scanf("%d",&n);
for(int i=1,x;i<=n;i++) scanf("%d",&x),f[x]++;
for(int j=0;j<20;j++)
{
for(int i=(1<<20)-1;~i;i--) if(!((i>>j)&1)) f[i]+=f[i|(1<<j)];
}
for(int i=0;i<(1<<20);i++) g[i]=Add(qpow(2,f[i]),-1);
ll Ans=0;
for(int i=0;i<(1<<20);i++) Ans=Add(Ans,Mul(g[i],PopCount(i)&1?-1:1));
printf("%lld",Ans);
return 0;
}