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

posted @ 2020-04-18 17:16  LuitaryiJack  阅读(217)  评论(0编辑  收藏  举报