「CF1336E2」Chiori and Doll Picking (hard version)

「CF1336E2」Chiori and Doll Picking (hard version)

给定 $n$ 个整数 $\langle a_1, a_2 … a_n \rangle$,在 $[0; 2^m)$ 的范围内。对于 $k \in [0; m]$,求选出一个子集使得异或和的二进制表示有 $k$ 个 $1$ 的方案数。

$1 \leq n \leq 2 \times 10^5,\ 0 \leq m \leq 53$。

0x01

定义:

  • $\operatorname{popcount}(x)$ 表示 $x$ 的二进制表示下 $1$ 的个数
  • $\langle i, j \rangle = \operatorname{popcount(i\ \&\ j)}$

对于线性基 $S$,定义:

  • $\operatorname{span}(S)$ 表示 $S$ 张成的向量空间
  • $F(S) = \sum_{x \in \operatorname{span}(S)} z^x$
  • $P(S) = \sum_{x \in \operatorname{span}(S)} z^{\operatorname{popcount}(x)}$

对于此题,定义

  • $A$ 为由题中给定数得到的线性基

首先你已经会了一个 $O(2^{\operatorname{rank}(A)})$ 的暴力,下文我们介绍一种 $O(2^{m-\operatorname{rank}(A)})$ 的算法,就可以通过分治在 $O(2^{m/2})$ 的时间复杂度内通过本题。

0x02

由线性基的基本性质,可以得到:

在此基础上枚举 $x \in \operatorname{span}(A)$ 有

由于是按位相乘,考虑方程 $x^2=x+1$ 的实根仅有

故 $[z^i] \operatorname{FWT}(F(A)) \in \{0, 2^{\operatorname{rank}(A)}\}$。

0x03

让我们再回到 $\operatorname{FWT}$ 运算本身的意义:

如果存在 $x$ 使得 $(-1)^{\langle i,x \rangle} = -1$,则 $\operatorname{FWT}(A)_i$ 只能为 $0$。

$\langle x,y \rangle$ 和 $\oplus$ 运算满足结合律:

可以通过把 $\&$ 理解为二进制按位乘法,$\oplus$ 理解为二进制不进位加法来证明。

故我们只需检验 $A$ 中的每个基底而非 $\operatorname{span}(A)$ 即可判断这一位的值。

0x04

定义 $A$ 的正交线性基为 $B$,使得对于所有 $x \in \operatorname{span}(A), y \in \operatorname{span}(B)$,满足 $\operatorname{popcount(x \& y)}$ 是偶数,且 $\operatorname{rank}(A) + \operatorname{rank}(B) = m$。

根据前面的引理,有

一种简单的正交线性基构造方式是

用高斯消元整理关键位,旋转右上角到左下角得到。

证明可以考虑图中圈出矩形的左上角和右上角一定为 $1$,而两向量的异或的 $\operatorname{popcount}$ 为偶数,那么左下角和右上角的数要么全为 $0$,要么全为 $1$。

0x05

知道了正交线性基怎么求,如何计算答案呢?

考虑用 $\operatorname{FWT}$ 表示答案统计:

其中 $G^c$ 表示 $\sum_{x \geq 0} z^x [\operatorname{popcount}(x)=c]$。

其中:

由于 $\operatorname{FWT}(F(A)) = F(B) \cdot 2^k$,而 $B$ 中的元素只有 $2^{\operatorname{rank}(B)} = 2^{m - \operatorname{rank}(A)}$ 个。故通过暴力得到 $P(B)$,即可通过组合数计算得 $P(A)$。

0x06

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include<bits/stdc++.h>
template<class T> inline void read(T &x){
x=0; register char c=getchar(); register bool f=0;
while(!isdigit(c))f^=c=='-',c=getchar();
while(isdigit(c))x=x*10+c-'0',c=getchar(); if(f)x=-x;
}
const int N=60,mod=998244353;
int n,m,k,t,c[N];
long long f[N],g[N];
struct z {
int x;
z(int x=0):x(x){}
friend inline z operator*(z a,z b){return (long long)a.x*b.x%mod;}
friend inline z operator-(z a,z b){return (a.x-=b.x)<0?a.x+mod:a.x;}
friend inline z operator+(z a,z b){return (a.x+=b.x)>=mod?a.x-mod:a.x;}
}p[N],q[N],fac[N],ifac[N];
inline z C(int n,int m){return n<m?0:fac[n]*ifac[m]*ifac[n-m];}
inline z fpow(z a,int b){z s=1;for(;b;b>>=1,a=a*a)if(b&1)s=s*a;return s;}
inline void insert(long long x){for(int i=m-1;i>=0;i--)if((x>>i)&1){if(f[i])x^=f[i]; else {f[i]=x; return;}}}
int main(){
read(n),read(m);
for(int i=1;i<=n;i++){long long x; read(x),insert(x);}
for(int i=0;i<m;i++)for(int j=i+1;j<m;j++)if((f[j]>>i)&1)f[j]^=f[i];
for(int i=0;i<m;i++)if(f[i])c[i]=k++;
for(int i=0;i<m;i++)if(!f[i])c[i]=k+(t++);
for(int i=0;i<m;i++)if(f[i])for(int j=0;j<m;j++)if((f[i]>>j)&1)g[c[i]]|=1ll<<c[j];
for(int i=0;i<k;i++)for(int j=k;j<m;j++)if((g[i]>>j)&1)g[j]|=1ll<<i;
for(int i=k;i<m;i++)g[i]|=1ll<<i;
if(k<=((m+1)>>1)){
std::function<void(int,long long)> dfs=[&](int i,long long s){
if(i>=k){p[__builtin_popcountll(s)].x++;return;}
dfs(i+1,s),dfs(i+1,s^g[i]);
};
dfs(0,0ll);
}else{
std::function<void(int,long long)> dfs=[&](int i,long long s){
if(i>=m){q[__builtin_popcountll(s)].x++;return;}
dfs(i+1,s),dfs(i+1,s^g[i]);
};
dfs(k,0ll);
fac[0]=ifac[0]=ifac[1]=1;
for(int i=1;i<=m;i++)fac[i]=fac[i-1]*i;
for(int i=2;i<=m;i++)ifac[i]=(mod-mod/i)*ifac[mod%i];
for(int i=1;i<=m;i++)ifac[i]=ifac[i-1]*ifac[i];
for(int c=0;c<=m;c++)for(int d=0;d<=m;d++)for(int i=0;i<=c;i++){
p[c]=p[c]+fpow(2,mod-1+k-m)*q[d]*(i&1?mod-1:1)*C(d,i)*C(m-d,c-i);
}
}
for(int i=0;i<=m;i++)printf("%d%c",(p[i]*fpow(2,n-k)).x," \n"[i==m]);
}