• 高维前缀和学习笔记 / CF449D Jzzhu and Numbers 题解


    高维前缀和

    \(~~~~\) 高维前缀和是用于解决有关子集或超集和问题的一种算法(或者说技巧?)

    \(~~~~\) 形式化地,即求:

    \[\sum_{i=0}^{2^n-1}\sum_{j \subset i} a_j \]

    \(~~~~\)

    \[\sum_{i=0}^{2^n-1}\sum_{j \supset i} a_j \]

    \(~~~~\) 即每一个集合的子集或超集和,这里把一个数理解为状压的集合方案。

    \(~~~~\) 一般来说,这样暴力做的时间复杂度为 \(\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;
    }
    
  • 相关阅读:
    AutoFac实现AOP扩展
    Nginx缓存、本地responseCache响应式缓存、ResourceFilter缓存。
    缓存基于http协议的前端缓存
    读取excel表格内容,处理后写输出xt文件上
    缓存memoryCache和redis分布式缓存
    Java 线上诊断工具Arthas
    c语言把16进制文本转16进制数组
    vue中使用tinymce,以及插件 powerpaste 的使用
    Go Concurrency Patterns: Pipelines and cancellation 孙龙
    golang 详解协程——errgroup 孙龙
  • 原文地址:https://www.cnblogs.com/Azazel/p/14584435.html
Copyright © 2020-2023  润新知