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\n",(a[0]+P-1)%P); } return 0; }
愿你出走半生,归来仍是少年