FWT 学习笔记
FWT 学习笔记
我们要计算如下式子,其中 \(\otimes\) 是一种位运算,这个式子又叫做位运算卷积。
\[C_i=\sum _{j\otimes k=i} A_j\times B_k
\]
暴力枚举可以做到 \(O(n^2)\),可以使用 \(FWT\) 做到 \(O(n\log n)\)。
类似于 FFT 考虑如下过程。
- 将 \(A,B\) 通过 FWT 变换成 \(A',B'\)。\(O(n\log n)\)。
- 计算 \(C'=A'\times B'\),其中 $\times $ 表示逐项相乘。\(O(n)\)。
- 将 \(C'\) 通过 FWT 逆变换成 \(C\)。\(O(n\log n)\)。
下文中的一些记号约定:\(\lor\) 表示按位或,\(\land\) 表示按位与,\(\oplus\) 表示按位异或,\(popcnt(i)\) 表示 \(i\) 中 \(1\) 的个数。
或卷积
我们要计算
\[C_i=\sum _{j\lor k=i} A_j\times B_k
\]
我们尝试构造 \(A'\) 满足以下式子。
\[A'_i=\sum_{j\lor i=i} A_j
\]
其本质上是子集的和。我们尝试将变换后的序列相乘发现它满足很好的性质:
\[\begin{align}
A'\times B' &= \sum _{j\lor i=i}\sum_{k\lor i=i} A_jB_k\\
&= \sum_{i\lor(j\lor k)=i} A_jB_k \\
&= C'
\end{align}
\]
如何变换呢?子集和我们很熟悉了,可以用高维前缀和的技巧即 SOS DP 实现。给出实现,其中 inc 为带模加法,\(n\) 为 \(2\) 的幂:
void fwt_or(int *a,int opt) {
if(opt==1) {fu(i,0,n) fu(j,0,1<<n) if(j>>i&1) inc(a[j],a[j^1<<i]);}
else {fu(i,0,n) fd(j,(1<<n)-1,0) if(j>>i&1) inc(a[j],mod-a[j^1<<i]);}
}
但是当然你可以使用网上的常规写法,自行百度。
与卷积
我们要计算
\[C_i=\sum _{j\land k=i} A_j\times B_k
\]
类似或卷积构造 \(A'\)。
\[A'_i=\sum _{j\land i=i} A_i
\]
其本质上是超集的和。我们再证明一下它的正确性。
\[\begin{align}
A'\times B' &= \sum _{j\land i=i}\sum_{k\land i=i} A_jB_k\\
&= \sum_{i\land(j\land k)} A_jB_k \\
&= C'
\end{align}
\]
超集和的实现一样是 SOS DP。实现如下:
void fwt_and(int *a,int opt) {
if(opt==1) {fu(i,0,n) fd(j,(1<<n)-1,0) if(!(j>>i&1)) inc(a[j],a[j^1<<i]);}
else {fu(i,0,n) fu(j,0,1<<n) if(!(j>>i&1)) inc(a[j],mod-a[j^1<<i]);}
}
但是当然你可以使用网上的常规写法,自行百度。
异或卷积
我们要计算
\[C_i=\sum _{j\oplus k=i} A_j\times B_k
\]
异或卷积稍微麻烦一些,我们定义运算 \(i\otimes j=popcnt(i\land j)\bmod 2\)。它满足性质:
\[(x\otimes y)\oplus(x\otimes z)=x\otimes(y\oplus z)
\]
证明分讨一下即可,自行百度。
我们定义一下变换。
\[A_i'=\sum _{i\otimes j=0} A_j-\sum_{i\otimes j=1} A_j
\]
我们来证明一下卷积的正确性。
\[\begin{align}
A'\times B' &= (\sum _{i\otimes j=0} A_j-\sum_{i\otimes j=1} A_j)(\sum _{i\otimes k=0} B_k-\sum_{i\otimes k=1} B_k) \\
&= (\sum _{i\otimes j=0}\sum_{i\otimes k=0}A_jB_k+\sum _{i\otimes j=1}\sum_{i\otimes k=1}A_jB_k)-(\sum _{i\otimes j=0}\sum_{i\otimes k=1}A_jB_k+\sum _{i\otimes j=1}\sum_{i\otimes k=0}A_jB_k)
\\
&= \sum_{i\otimes(j\oplus k)=0}A_jB_k-\sum_ {i\otimes(j\oplus k)=1}A_jB_k
\\
&= C'
\end{align}
\]
我们考虑怎么计算变换。有顺变换公式:
\[A'=merge(A'_0+A'_1,A'_0-A'_1)
\]
逆变换:
\[A=\frac {merge(A_0+A_1,A_0-A_1)}2
\]
其中 \(merge\) 表示拼接,\(+,-\) 表示逐项相加减,我们枚举一个位数 \(i\),\(A_0\) 为第 \(i\) 位为 \(0\) 的子序列,\(A_1\) 为第 \(i\) 位为 \(1\) 的子序列。
实现如下:
void fwt_xor(int *a,int opt) {
if(opt==-1) opt=pow1(2,mod-2);
for(int i=1;i<1<<n;i<<=1) {
for(int j=0;j<1<<n;j+=i<<1) {
fu(k,j,j+i) {
int t=a[k+i];
a[k+i]=(a[k]+mod-a[k+i])%mod;
inc(a[k],t);
}
}
fu(j,0,1<<n) a[j]=(ll)a[j]*opt%mod;
}
}

浙公网安备 33010602011771号