给出 (n) 个数,从中选出两个互不相交的集合,使得第一个集合与第二个集合内的数的异或和相等。求总方案数,对 (998244353) 取模。(nleq 10^6,0leq a_ileq 10^6)
如果两个集合的异或和相等,那么它们再异或一下即为 (0)。原问题转化为求选出一个异或和为 (0) 的集合并分为两个集合的方案数(去除两个集合都是空集的情况)。
设 (f[i][j])表示前 (i) 个数中选出的数的异或和为 (j) 的方案数;
那么就有 (f[i][j]=f[i−1][j]+2 imes f[i−1][j {
m xor} a[i]]) ;
这是一个异或卷积的形式,相当于每次卷的是:(b[0]=1,b[a[i]]=2) ;
考虑对这个过程进行 FWT ,那么:
(0) 对每个位置的贡献都是 (1) ,即对应 (f[i-1][j]) 的贡献;
(a[i]) 对某些位置的贡献是 (2) (正的贡献) ,对某些位置的贡献是 (-2) (负的贡献)。
所以每次卷上的 (b) 数组的每个数都是 (-1) 或 (3) 。
和的FWT等于FWT的和。
因此把每个 (a_i) 对应的 (b) 数组求和后进行 FWT ,那么就知道了每个位置 FWT 的和;
最终再逆FWT回来即可。
时间复杂度 (mathcal{O}(nlogn)) 。
how to 卡常:
FWT 过程中可以不取模;
最后不用逆 FWT :
正 FWT :(a'_j=sumlimits_i (−1)^{{ m popcount}(i&j) }a_i)
设 FWT 的长度为 (2^K) ,我们观察到,如果我们把所有位置的的值加起来,下标为 (0) 的贡献永远是正的,其他下标 (i) 的贡献正好抵消 ( (-1) 和 (+1) 均为 (2^{K-1}) 种);所以我们只用把计算完贡献的所有位置加起来,然后 (ans/=2^K) 即可
int n,K,len,ans,mx,a[N],p[N];
inline int qpow(int a,int b) { R ret=1;
for(;b;b>>=1,a=1ll*a*a%M) if(b&1) ret=1ll*ret*a%M; return ret;
}
inline void cxor(int* a) {
for(R l=1;l<K;l<<=1)
for(R len=l<<1,i=0,x,y;i<K;i+=len)
for(R j=0;j<l;++j) {
x=a[i+j],y=a[i+j+l];
a[i+j]=x+y,a[i+j+l]=x-y;
}
}
inline void main() {
n=g(); for(R i=1,x;i<=n;++i)
x=g(),++a[x],mx=max(x,mx);
K=1; while(K<=mx) K<<=1;
p[0]=1; for(R i=1;i<=n;++i) p[i]=3ll*p[i-1]%M;
cxor(a);
//做完异或卷积后,我们就知道每个位置对应的 正的贡献 减 负的贡献 的值是多少辣
for(R i=0,x;i<K;++i) {
x=(a[i]+n)/2;
ans=(ans+p[x]*((n-x)&1?-1:1))%M;
}
printf("%d
",(1ll*ans*qpow(K,M-2)%M-1+M)%M);
}
2020.04.18