【CF449D】Jzzhu and Numbers

本文介绍了一种使用FWT(Fast Walsh-Hadamard Transform)算法解决背包问题的方法,尤其是在按位与(AND)操作下的背包问题。通过将传统背包问题转化为FWT的卷积形式,可以更高效地计算出所有可能的组合结果。文章详细解释了FWT正反变换的过程,并给出了具体的C++代码实现。

题目

提供一个非容斥做法——\(FWT\)

我们发现我们要求的东西就是一个背包,只不过是在\(and\)意义下的

自然有

\[dp_{i,j}=\sum_{k\&a_i=j}dp_{i-1,k}+dp_{i-1,j}\]

我们发现这个柿子本质上就是一个和卷积

于是两边取\(fwt\),我们就可以得到一个暴力\(fwt\)的代码

for(re int i=1;i<=n;i++) {
        memset(A,0,sizeof(A));
        A[a[i]]=1;Fwtand(A,1);
        for(re int j=0;j<len;j++) S[j]=S[j]+S[j]*A[j];
}
Fwtand(S,-1);

我们发现其实\(fwt\)的正向变换求得是超集和,又因为我们这里只有一个\(A[a[i]]=1\),所以所有\(A_j\)最多就是\(1\)\(A_j=1\)的时候会使得\(S_j\)翻倍,所以整体下来我们只需要关心\(S_j\)翻了多少倍

显然有多个\(a_i\)\(j\)的超集就能翻多少倍,于是我们对\(A\)整体来一个\(fwt\)就好了

但是非常蛇皮的一点就是当\(a\)全是\(0\)的时候会把空集算入答案,因此需要特判一下

代码

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#define re register
#define LL long long
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
const int maxn=2500005;
const int mod=1e9+7;
inline int read() {
    char c=getchar();int x=0;while(c<'0'||x>'9') c=getchar();
    while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+c-48,c=getchar();return x;
}
int len,n,M;
LL S[maxn],A[maxn],a[maxn],pw[maxn];
inline void Fwt(LL *f,int o,int mod) {
    for(re int i=2;i<=len;i<<=1)
        for(re int ln=i>>1,l=0;l<len;l+=i)
            for(re int x=l;x<l+ln;++x)
                f[x]+=o*f[ln+x],f[x]=(f[x]+mod)%mod;
}
int main() {
    n=read();
    for(re int i=1;i<=n;i++) a[i]=read(),M=max(M,a[i]);
    len=1;while(len<=M) len<<=1;
    pw[0]=1;
    for(re int i=1;i<=n;i++) pw[i]=(pw[i-1]+pw[i-1])%mod;
    for(re int i=1;i<=n;i++) A[a[i]]++;
    Fwt(A,1,mod-1);
    for(re int i=0;i<len;i++) A[i]=pw[A[i]];
    Fwt(A,-1,mod);
    if(A[0]==pw[n]) std::cout<<A[0]-1;
    else std::cout<<A[0];
    return 0;
}

转载于:https://www.cnblogs.com/asuldb/p/10687979.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值