FWT

\(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 \]
于是我们需要快速地求出子集和。

有两种主要的方法,一种是高维前缀和,可以看这里

另一种是分治的算法,借一张网上的图,箭头表示累加,应该就能理解了。

FWT

变回去是把累加改为减就可以了。

代码如下:

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)》

上一篇:至少与至少


下一篇:容斥 反演