UOJ 310 黎明前的巧克力(FWT)

【题目链接】 http://uoj.ac/problem/310

【题目大意】

  给出一个数集,A从中选择一些数,B从中选择一些数,不能同时不选
  要求两者选择的数异或和为0,问方案数

【题解】

  题目等价于选取一个非空且xor为0的集合并将其拆分为两个子集的方案数
  用dp表示xor为j的方案数,易得dp方程dp[i][j]=dp[i-1][j]+2*dp[i-1][j^a[i]]
  该式等价于dp数组与只有两个元素有值的g[0]=1,g[a[i]]=2的数组做卷积运算
  对g数组进行反演可以发现每次卷积的数组只包含3和-1,
  那么我们只要知道对一个下标来说,做的n次卷积中有几个3和-1,
  就能够直接乘算出答案,根据FWT的和等于和的FWT,我们将多次需要做卷积的数组相加,
  一并做FWT,得到他们和的反演值,在每个位置解关于3和-1的二元一次方程组,
  再将其替换为正确值,最后FWT求逆之后下标为0的答案减去1就是答案,
  减一是因为两个人取数不能同时为空。

【代码】

#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int P=998244353;
const int inv2=(P+1)>>1;
const int N=2000000;
void FWT(int*a,int n){
    for(int d=1;d<n;d<<=1)for(int m=d<<1,i=0;i<n;i+=m)for(int j=0;j<d;j++){
        int x=a[i+j],y=a[i+j+d];
        a[i+j]=(x+y)%P,a[i+j+d]=(x-y+P)%P;
    }
}
void UFWT(int*a,int n){
    for(int d=1;d<n;d<<=1)for(int m=d<<1,i=0;i<n;i+=m)for(int j=0;j<d;j++){
        int x=a[i+j],y=a[i+j+d];
        a[i+j]=1LL*(x+y)*inv2%P,a[i+j+d]=1LL*(x-y)*inv2%P;
    }
}
int n,x,mx,pw[N],a[N];
int main(){
    pw[0]=1;
    for(int i=1;i<N;i++)pw[i]=3LL*pw[i-1]%P;
    while(~scanf("%d",&n)){
    	memset(a,0,sizeof(a));
        for(int i=mx=1;i<=n;i++){
            scanf("%d",&x);
            a[0]++; a[x]+=2;
            mx=max(mx,x);
        }
        int m=1;
        while(m<=mx)m<<=1;
        FWT(a,m);
        for(int i=0;i<m;i++){
            x=(3ll*n+P-a[i])*inv2%P*inv2%P;
            a[i]=(x&1)?(P-pw[n-x])%P:pw[n-x];
        }
        UFWT(a,m);
        printf("%d
",(a[0]+P-1)%P);
    }
    return 0;
}
原文地址:https://www.cnblogs.com/forever97/p/uoj310.html