\(FFT\)(快速傅里叶变换)求的是卷积,也就是
\[
C_k=\sum_{i+j=k}A_iB_j
\]
那么\(FWT\)(快速沃尔什变换)求的就是子集卷积,也就是
\[
C_{k}=\sum_{i \oplus j=k} A_{i} B_{j}
\]
\(\oplus\)指按位运算\(or,and,xor\)
其实\(or,and\)卷积应该是\(FMT\)(快速莫比乌斯变换),因为思想较为类似,故写在一篇博客中。
\(FMT\)
先介绍\(or\)卷积,因为感性上理解\(or\)和\(and\)操作较为类似,所以两者实际是等价的。
根据\(FFT\)的思路,我们同样需要变换出一个函数使得
\[
FMT(A)_i\times FMT(B)_i=FMT(C)_i
\]
然后莫比乌斯变换就是构造了下面这个式子:
\[
FMT(A)_n=\sum _{i \subseteq n}A_i
\]
证明也很简单:
\[
\sum_{i \subseteq n} A_{i} \sum_{j \subseteq n} B_{j}=\sum_{i, j \subseteq n} A_{i} B_{j}=\sum_{k \subseteq n} \sum_{i | j=k} A_{i} B_{j}
\]
注意到
\[
C_k=\sum_{i\oplus j = k}A_iB_j
\]
那么
\[
FMT(A)_n\times FMT(b)_n=\sum_{k\subseteq n}=FMT(C)_n
\]
于是我们需要快速地求出子集和。
有两种主要的方法,一种是高维前缀和,可以看这里。
另一种是分治的算法,借一张网上的图,箭头表示累加,应该就能理解了。
变回去是把累加改为减就可以了。
代码如下:
void FMT(int *a, int n, int x)
{
for (int i = 1; i <= 1 << n; i <<= 1)
for (int j = 0; j < 1 << n; j += (i << 1))
for (int k = 0; k < i; ++k)
a[i + j + k] += a[j + k] * x;
}
\(and\)卷积也很类似
\[
FMT(A)_n=\sum_{n\subseteq i}A_i
\]
代码如下:
void FMT(int *a, int n, int x)
{
for (int i = 1; i <= 1 << n; i <<= 1)
for (int j = 0; j < 1 << n; j += (i << 1))
for (int k = 0; k < i; ++k)
a[j + k] += a[i + j + k] * x;
}
\(FWT\)
未完待续。。。
参考文献:《真正理解快速沃尔什变换/快速莫比乌斯变换(FWT|FMT)》