【UOJ#310】【UNR#2】黎明前的巧克力

FWT

题目大意:

给定 $n$ 个数 $a_1,a_2,a_3,\ldots,a_n$。有两个人,要在其中选出两个集合(交换算两种方案),使得集合没有交集,并且两个集合的 xor 和相等。允许一个人选空集,不允许两个人同时选空集。

求方案数。

题解:

其实相当于两个人选的数的 xor 和为 $0$。

那么对每个数进行考虑,要么两个人都不选,要么其中一个人选了。

于是对于 $a_i$,有一种选法使当前的 xor 和不变,两种选法使当前的 xor 和异或上 $a_i$。

那么令 $F_{i,0}=1$,$F_{i,a_i}=2$,然后对所有 $F_i$ 做集合并卷积就好了。

这样复杂度显然爆炸。

考虑对 $A$ 进行 FWT 后得到的 $A’$,满足 $A’[k]=\sum\limits_{i}(-1)^{\mathrm{popcount}(i\&k)}A[i]$。

所以 $F_i$ 的每一位要么是 $-1$ 要么是 $3$。

那么最终点积出来的数组 $G$ 可以表示为 $G[k]=3^x\cdot (-1)^y$ 的形式。

接下来我们只需要知道每一位对应的 $x$ 和 $y$ 是多少就好了。

首先有 $x+y=n$。我们只需要知道 $x-y$ 是多少就能求出 $x$ 和 $y$。

对于 $a_i$,我们只要令 $F_{a_i}=1$,然后对 $F$ 做 FWT 就可以知道它对每位的贡献是 $1$ 还是 $-1$。而 $\mathrm{FWT}(A+B)=\mathrm{FWT}(A)+\mathrm{FWT}(B)$。

所以令 $H[i]$ 表示 $i$ 的出现次数,然后对 $H$ 做 FWT 就可以得到每个位置的 $x-y$ 了。

接着用快速幂求出 $G$ 的每个位置的值,对其 IFWT 即可。

时间复杂度 $O(n+V\log V)$。

Code:

#include<iostream>
using namespace std;
const int md=998244353,M=1048576;
typedef long long LL;
int F[M],n;
inline void upd(int&a){a+=a>>31&md;}
inline int pow(int a,int b){
    int res=1;
    for(;b;b>>=1,a=(LL)a*a%md)if(b&1)res=(LL)res*a%md;
    return res;
}
void FWT(int*a){
    for(int i=1;i<M;i<<=1)
    for(int j=0;j<M;j+=i<<1)
    for(int k=0;k<i;++k){
        const int x=a[j+k],y=a[j+k+i];
        upd(a[j+k]+=y-md),upd(a[j+k+i]=x-y);
    }
}
int main(){
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    cin>>n;
    for(int i=0,x;i<n;++i)
    cin>>x,++F[x];
    FWT(F);
    for(int i=0;i<M;++i){
        int x=(n+F[i])*499122177LL%md,y=(n-F[i]+md)*499122177LL%md;
        F[i]=pow(3,x)*((y&1)?md-1LL:1LL)%md;
    }
    FWT(F);
    cout<<(F[0]*998243401LL+md-1)%md<<'\n';
    return 0;
}