【题解】[UOJ 310] UNR#2 黎明前的巧克力【FWT】

题目链接

题意

将全集 \(S\) 分为 \(S_1,S_2,S_3\),要求 \(S_1,S_2\) 异或和相等且不全为空。求方案数。\(|S|,x\leq 10^6,\forall a\in S\)

题解

问题可转化为 \(T\subseteq S,T\neq \varnothing\)\(T\) 异或和为 \(0\)\(T\) 中每个元素贡献 \(2\) 的系数。

\(a_i\in S\) 可以写作 \(F_i(x)=x^{\varnothing}+2x^{a_i}\) 的集合幂级数,暴力异或卷积显然复杂度爆炸。

\(F_i(x)\) FWT 后每个位置为 \(-1\)\(3\),我们只用统计每个位置乘了多少 \(3\)\(-1\)。将所有幂级数加起来 FWT,等于 FWT 后加起来,于是解一个二元一次方程组,便可以知道每个位置有多少 \(3\)\(-1\),以此求出 FWT 后相乘的结果,IFWT 即可。

#include<bits/stdc++.h>
using namespace std;
const int N=21,mod=998244353,inv4=(mod*3ll+1)/4;
int a[1<<N],n;
int b[1<<N];
int c[1<<N];

int qpow(int x,int y){
    int ans=1;
    while(y){
        if(y&1)ans=ans*1ll*x%mod;
        x=x*1ll*x%mod;
        y>>=1;
    }
    return ans;
}
void fwt(int *a,int m,int tp){
    for(int i=1;i<1<<m;i<<=1){
        for(int j=0;j<1<<m;j+=i<<1){
            for(int k=0;k<i;k++){
                int x=a[j+k],y=a[i+j+k];
                // cerr<<". "<<j+k<<" "<<i+j+k<<endl;
                a[j+k]=x+y;a[i+j+k]=x-y;
                a[j+k]>=mod&&(a[j+k]-=mod);
                a[i+j+k]<0&&(a[i+j+k]+=mod);
            }
        }
    }
    if(tp==-1){
        int q=qpow(1<<m,mod-2);
        for(int i=0;i<1<<m;i++)
            a[i]=a[i]*1ll*q%mod;
    }
}
int main(){
    scanf("%d",&n);
    for(int i=0;i<n;i++)scanf("%d",a+i);
    for(int i=0;i<n;i++)b[0]++,b[a[i]]+=2;//随意,只要后面解方程改改即可
    int m=0;
    for(int i=0;i<n;i++)while(1<<m<=a[i])m++;
    // n=1<<m;
    fwt(b,m,1);
    for(int i=0;i<1<<m;i++){
        int cnt3=(n+b[i])*1ll*inv4%mod,cnt1=n-cnt3;
        // cerr<<"> "<<b[i]<<"  "<<cnt3<<" "<<cnt1<<endl;
        c[i]=qpow(3,cnt3)*1ll*(cnt1%2?mod-1:1)%mod;
    }
    fwt(c,m,-1);
    cout<<(c[0]-1+mod)%mod<<endl;
}

posted @ 2020-12-25 21:14  破壁人五号  阅读(72)  评论(0编辑  收藏  举报