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

给出 (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

原文地址:https://www.cnblogs.com/Jackpei/p/12726892.html