FWT的简介
一般 FWT \text{FWT} FWT用来解决一下问题:
- C k = ∑ i ∣ j = k A i B j C_k=\sum_{i|j=k}A_iB_j Ck=∑i∣j=kAiBj
- C k = ∑ i & j = k A i B j C_k=\sum_{i\&j=k}A_iB_j Ck=∑i&j=kAiBj
- C k = ∑ i ⊕ j = k A i B j C_k=\sum_{i\oplus j=k}A_iB_j Ck=∑i⊕j=kAiBj
实现的大概思路就是就是先把他们转化成 fwt(A) \text{fwt(A)} fwt(A)(类似 FFT \text{FFT} FFT的点值表达),然后对应为相乘,最后在还原为多项式(整个过程很类似与快速傅里叶变换)
主要的就是我们设计的正变换要满足可以直接相乘得到
C
C
C 的正变换,比如
o
r
or
or 卷积的证明:
C
i
′
=
∑
j
∈
i
C
j
=
∑
j
∈
i
∑
a
∣
b
=
j
A
a
B
b
C'_i=\sum_{j\in i}C_{j}=\sum_{j\in i}\sum_{a|b=j}A_{a}B_b
Ci′=j∈i∑Cj=j∈i∑a∣b=j∑AaBb
=
∑
a
∈
i
A
a
∑
b
∈
i
B
b
=
A
a
′
B
b
′
=\sum_{a\in i}A_a\sum_{b\in i} B_b=A_a'B_b'
=a∈i∑Aab∈i∑Bb=Aa′Bb′
or 卷积
现在要做到这个的快速卷积: C k = ∑ i ∣ j = k A i B j C_k=\sum_{i|j=k}A_iB_j Ck=∑i∣j=kAiBj
定义 A ∣ B A|B A∣B为多项式的 or \text{or} or卷积,显然 A ∣ B = B ∣ A A|B=B|A A∣B=B∣A(交换律), ( A + B ) ∣ C = A ∣ C + B ∣ C (A+B)|C=A|C+B|C (A+B)∣C=A∣C+B∣C(结合律)
定义 f w t ( A ) [ k ] = ∑ i ∣ k A i fwt(A)[k]=\sum_{i|k}A_i fwt(A)[k]=∑i∣kAi,因为我们要让 f w t ( C ) = f w t ( A ) × f w t ( B ) fwt(C)=fwt(A)\times fwt(B) fwt(C)=fwt(A)×fwt(B),这个基于的原理就是若 i ∣ k = k i|k=k i∣k=k, j ∣ k = k j|k=k j∣k=k,就能推出 ( i ∣ j ) ∣ k = k (i|j)|k=k (i∣j)∣k=k,那么原来是 k k k子集的或之后还是 k k k子集。
现在我们来研究如何计算
f
w
t
(
A
)
fwt(A)
fwt(A),类似
FFT
\text{FFT}
FFT地用分治的方法,设
A
0
A_0
A0为当前位为
0
0
0的项,
A
1
A_1
A1就是剩下部分:
f
w
t
(
A
)
=
{
(
f
w
t
(
A
0
)
,
f
w
t
(
A
0
+
A
1
)
)
n
>
0
A
n
=
0
fwt(A)=\begin{cases}(fwt(A_0),fwt(A_0+A_1))& n>0\\A& n=0\end{cases}
fwt(A)={(fwt(A0),fwt(A0+A1))An>0n=0根据
f
w
t
(
A
)
fwt(A)
fwt(A)的定义可以知道:
f
w
t
(
A
+
B
)
=
f
w
t
(
A
)
+
f
w
t
(
B
)
fwt(A+B)=fwt(A)+fwt(B)
fwt(A+B)=fwt(A)+fwt(B),根据定义也可以知道如果最高位为
1
1
1,那么就把
f
w
t
(
A
0
+
A
1
)
fwt(A_0+A_1)
fwt(A0+A1)算作
A
A
A的后半部分就可以了,为
0
0
0的话子集就是
0
0
0
现在我们来证明一下
f
w
t
(
A
∣
B
)
=
f
w
t
(
A
)
×
f
w
t
(
B
)
fwt(A|B)=fwt(A)\times fwt(B)
fwt(A∣B)=fwt(A)×fwt(B):
f
w
t
(
A
∣
B
)
=
f
w
t
(
(
A
∣
B
)
0
,
(
A
∣
B
)
1
)
=
f
w
t
(
A
0
∣
B
0
,
A
0
∣
B
1
+
A
1
∣
B
0
+
A
1
∣
B
1
)
=
(
f
w
t
(
A
0
∣
B
0
)
,
f
w
t
(
A
0
∣
B
0
+
A
1
∣
B
0
+
A
0
∣
B
1
+
A
1
B
1
)
)
=
(
f
w
t
(
A
0
)
×
f
w
t
(
B
0
)
,
f
w
t
(
A
0
+
A
1
)
×
f
w
t
(
B
0
+
B
1
)
)
=
(
f
w
t
(
A
0
)
,
f
w
t
(
A
0
+
A
1
)
)
×
(
f
w
t
(
B
0
)
,
f
w
t
(
B
0
,
B
1
)
)
=
f
w
t
(
A
)
×
f
w
t
(
B
)
fwt(A|B)=fwt((A|B)_0,(A|B)_1)\\=fwt(A_0|B_0,A_0|B_1+A_1|B_0+A_1|B_1)\\=(fwt(A_0|B_0),fwt(A_0|B_0+A_1|B_0+A_0|B_1+A_1B_1))\\=(fwt(A_0)\times fwt(B_0),fwt(A_0+A_1)\times fwt(B_0+B_1))\\=(fwt(A_0),fwt(A_0+A_1))\times (fwt(B_0),fwt(B_0,B_1))\\=fwt(A)\times fwt(B)
fwt(A∣B)=fwt((A∣B)0,(A∣B)1)=fwt(A0∣B0,A0∣B1+A1∣B0+A1∣B1)=(fwt(A0∣B0),fwt(A0∣B0+A1∣B0+A0∣B1+A1B1))=(fwt(A0)×fwt(B0),fwt(A0+A1)×fwt(B0+B1))=(fwt(A0),fwt(A0+A1))×(fwt(B0),fwt(B0,B1))=fwt(A)×fwt(B)这里用到了数学归纳法,首先
n
=
0
n=0
n=0的情况肯定成立,然后我们假设较小的规模成立,以此推导更大的规模(这里是
1
2
\frac{1}{2}
21)
最后返回来的
d
f
w
t
dfwt
dfwt就是根据上面的
f
w
t
fwt
fwt设计的,变换如下:
d
f
w
t
(
A
)
=
{
d
f
w
t
(
A
0
)
,
d
f
w
t
(
A
1
−
A
0
)
n
>
1
A
n
=
0
dfwt(A)=\begin{cases}dfwt(A_0),dfwt(A_1-A_0)&n>1\\A&n=0\end{cases}
dfwt(A)={dfwt(A0),dfwt(A1−A0)An>1n=0
and 卷积
这个和上面的卷积极其类似,直接给出结论:
f
w
t
(
A
)
=
{
(
f
w
t
(
A
0
+
A
1
)
,
f
w
t
(
A
0
)
)
n
>
0
A
n
=
0
fwt(A)=\begin{cases}(fwt(A_0+A_1),fwt(A_0))& n>0\\A& n=0\end{cases}
fwt(A)={(fwt(A0+A1),fwt(A0))An>0n=0逆变换如下:
d
f
w
t
(
A
)
=
{
d
f
w
t
(
A
0
−
A
1
)
,
d
f
w
t
(
A
1
)
n
>
0
A
n
=
0
dfwt(A)=\begin{cases}dfwt(A_0-A_1),dfwt(A_1)&n>0\\A&n=0\end{cases}
dfwt(A)={dfwt(A0−A1),dfwt(A1)An>0n=0
xor 卷积
这就是重头戏了,我们要解决: C k = ∑ i ⊕ j = k A i B j C_k=\sum_{i\oplus j=k}A_iB_j Ck=∑i⊕j=kAiBj
先定义
f
w
t
(
A
)
[
x
]
=
∑
2
∣
d
(
x
∩
i
)
A
i
−
∑
2
∣
[
d
(
x
∩
i
)
−
1
]
A
i
fwt(A)[x]=\sum_{2|d(x\cap i)}A_i-\sum_{2|[d(x\cap i)-1]}A_i
fwt(A)[x]=∑2∣d(x∩i)Ai−∑2∣[d(x∩i)−1]Ai,
d
d
d是二进制
1
1
1的个数,这样定义是基于一个结论:
d
(
x
∩
(
i
⊕
j
)
)
=
d
(
x
∩
i
)
+
d
(
x
∩
j
)
−
2
d
(
x
∩
i
∩
j
)
d(x\cap (i\oplus j))=d(x\cap i)+d(x\cap j)-2d(x\cap i\cap j)
d(x∩(i⊕j))=d(x∩i)+d(x∩j)−2d(x∩i∩j)考虑每一位的合法性,就可以推知所有情况,这个自己枚举一下可能的组合就行了,然后:
f
w
t
(
A
)
[
x
]
⊕
f
w
t
(
B
)
[
x
]
=
∑
2
∣
d
(
x
∩
i
)
∑
2
∣
d
(
x
∩
j
)
A
i
B
j
−
∑
2
∣
[
d
(
x
∩
i
)
−
1
]
∑
2
∣
d
(
x
∩
j
)
A
i
B
j
.
.
.
.
.
.
fwt(A)[x]\oplus fwt(B)[x]=\sum_{2|d(x\cap i)}\sum_{2|d(x\cap j)}A_iB_j-\sum_{2|[d(x\cap i)-1]}\sum_{2|d(x\cap j)}A_iB_j......
fwt(A)[x]⊕fwt(B)[x]=2∣d(x∩i)∑2∣d(x∩j)∑AiBj−2∣[d(x∩i)−1]∑2∣d(x∩j)∑AiBj......你可以发现如果
d
(
x
∩
i
)
d(x\cap i)
d(x∩i)和
d
(
x
∩
j
)
d(x\cap j)
d(x∩j)奇偶性相同的话那么前面的符号是正,否则前面的符号是负,我们观察上面的结论,发现
d
(
x
∩
(
i
⊕
j
)
)
d(x\cap (i\oplus j))
d(x∩(i⊕j))和
d
(
x
∩
i
)
+
d
(
x
∩
j
)
d(x\cap i)+d(x\cap j)
d(x∩i)+d(x∩j)的奇偶性相同,恰好就对应了。
现在来考虑正变换,基本思路还是考虑最高位,左半边的话都选自己和选自己再选对半边都可以,而且不需要变号,因为不会产生新的
1
1
1位,两个都选右半边的话就需要变号,那么就这样算:
f
w
t
(
A
)
=
{
(
f
w
t
(
A
0
+
A
1
)
,
f
w
t
(
A
0
−
A
1
)
)
n
>
0
A
n
=
0
fwt(A)=\begin{cases}(fwt(A_0+A_1),fwt(A_0-A_1))&n>0\\A&n=0\end{cases}
fwt(A)={(fwt(A0+A1),fwt(A0−A1))An>0n=0然后逆变化就是:
i
f
w
t
(
A
)
=
{
(
i
f
w
t
(
A
0
+
A
1
)
2
,
i
f
w
t
(
A
0
−
A
1
)
2
)
n
>
0
A
n
=
0
ifwt(A)=\begin{cases}(\frac{ifwt(A_0+A_1)}{2},\frac{ifwt(A_0-A_1)}{2})&n>0\\A&n=0\end{cases}
ifwt(A)={(2ifwt(A0+A1),2ifwt(A0−A1))An>0n=0
然后贴一个板题的代码:
#include <cstdio>
const int M = 200005;
const int MOD = 998244353;
int read()
{
int num=0,flag=1;char c;
while((c=getchar())<'0'||c>'9')if(c=='-')flag=-1;
while(c>='0'&&c<='9')num=(num<<3)+(num<<1)+(c^48),c=getchar();
return num*flag;
}
int n,a[M],b[M],A[M],B[M],inv2=(MOD+1)/2;
void fwt_or(int *a,int op)
{
for(int i=1;i<n;i<<=1)
for(int p=i<<1,j=0;j<n;j+=p)
for(int k=0;k<i;k++)
{
if(op==1) a[i+j+k]=(a[i+j+k]+a[j+k])%MOD;
else a[i+j+k]=(a[i+j+k]-a[j+k]+MOD)%MOD;
}
}
void fwt_and(int *a,int op)
{
for(int i=1;i<n;i<<=1)
for(int p=i<<1,j=0;j<n;j+=p)
for(int k=0;k<i;k++)
{
if(op==1) a[j+k]=(a[j+k]+a[i+j+k])%MOD;
else a[j+k]=(a[j+k]-a[i+j+k]+MOD)%MOD;
}
}
void fwt_xor(int *a,int op)
{
for(int i=1;i<n;i<<=1)
for(int p=i<<1,j=0;j<n;j+=p)
for(int k=0;k<i;k++)
{
int x=a[j+k],y=a[i+j+k];
a[j+k]=(x+y)%MOD;
a[i+j+k]=(x+MOD-y)%MOD;
if(op==-1)
{
a[j+k]=1ll*a[j+k]*inv2%MOD;
a[i+j+k]=1ll*a[i+j+k]*inv2%MOD;
}
}
}
void init()
{
for(int i=0;i<n;i++)
A[i]=a[i],B[i]=b[i];
}
signed main()
{
n=1<<read();
for(int i=0;i<n;i++) a[i]=read();
for(int i=0;i<n;i++) b[i]=read();
init();
fwt_or(A,1);fwt_or(B,1);
for(int i=0;i<n;i++) A[i]=1ll*A[i]*B[i]%MOD;
fwt_or(A,-1);
for(int i=0;i<n;i++) printf("%d ",A[i]);
puts("");
init();
fwt_and(A,1);fwt_and(B,1);
for(int i=0;i<n;i++) A[i]=1ll*A[i]*B[i]%MOD;
fwt_and(A,-1);
for(int i=0;i<n;i++) printf("%d ",A[i]);
puts("");
init();
fwt_xor(A,1);fwt_xor(B,1);
for(int i=0;i<n;i++) A[i]=1ll*A[i]*B[i]%MOD;
fwt_xor(A,-1);
for(int i=0;i<n;i++) printf("%d ",A[i]);
puts("");
}