UOJ#310. 【UNR #2】黎明前的巧克力
给出 \(n\) 个数,从中选出两个互不相交的集合,使得第一个集合与第二个集合内的数的异或和相等。求总方案数,对 \(998244353\) 取模。\(n\leq 10^6,0\leq a_i\leq 10^6\)
如果两个集合的异或和相等,那么它们再异或一下即为 \(0\)。原问题转化为求选出一个异或和为 \(0\) 的集合并分为两个集合的方案数(去除两个集合都是空集的情况)。
设 \(f[i][j]\)表示前 \(i\) 个数中选出的数的异或和为 \(j\) 的方案数;
那么就有 \(f[i][j]=f[i−1][j]+2\times f[i−1][j\ {\rm 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=\sum\limits_i (−1)^{{\rm 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\n",(1ll*ans*qpow(K,M-2)%M-1+M)%M);
}
2020.04.18