快速沃尔什变换 FWT

1 定义

我们曾经学习过多项式乘法,其核心在于快速傅里叶变换 FFT。而本质上,多项式乘法实际上就是在对下标做加法卷积,如果要对下标做位运算卷积,则我们就需要引入快速沃尔什变换 FWT。

形式化的,给出两个序列 \(A_i,B_i\),定义位运算卷积为:

\[C_i=\sum_{i=j\odot k} A_jB_k \]

其中 \(\odot\) 是某种二元位运算,常见的形式就是按位与,按位或以及按位异或。

下文中默认序列长度 \(n\)\(2\) 的正整数次幂。

2 快速沃尔什变换 FWT

2.1 基本思想

和 FFT / NTT 类似的,我们希望求出一个对于原数组的变换 \(FWT(A)\),使得 \(FWT(C)_i=FWT(A)_i\times FWT(B)_i\),这样的话我们就可以直接 \(O(n)\) 对位乘得出 \(C\) 数组。现在的关键是怎样快速进行变换以及逆变换。

我们定义 \(c(i,j)\) 表示 \(A_j\)\(FWT(A)_i\) 的贡献系数,那么我们可以得到:

\[FWT(A)_i=\sum_{j=0}^{n-1} c(i,j) A_j \]

那么此时再根据我们 FWT 后序列对位相乘的要求,可以得出一个关键结论:

\[c(i,j)c(i,k)=c(i,j\odot k) \]

并且 \(c\) 函数的一个重要性质是他可以按位处理,也就是说对于 \(c(i,j)\),我们可以把它的二进制位拆出来,然后令 \(c(i,j)=\prod c(i_k,j_k)\),显然如果在每一位都满足上面的性质,那么乘起来之后也是完全正确的。

那么此时我们 \(c\) 两维的值域就变成了 \(0/1\),我们只需要构造出 \(c\) 似乎就可以解决问题了。

我们可以将上面 FWT 的定义式进行变形:

\[FWT(A)_i=\sum_{j=0}^{n/2-1} c(i,j) A_j+\sum_{j=n/2}^{n-1} c(i,j) A_j \]

考察后面的区别,发现只有最高位是不一样的,把 \(i,j\) 的最高位提出来,设去掉最高位后的 \(i,j\)\(i',j'\),则有:

\[FWT(A)_i=c(i_0,0)\sum_{j=0}^{n/2-1} c(i',j') A_j+c(i_0,1)\sum_{j=n/2}^{n-1} c(i',j') A_j \]

那么此时左右两边的变换就是完全一致的了,我们只需要根据 \(c\) 的值进行合并,那么我们就拥有了一个分治求解 FWT 的方法,而具体的 \(c\) 只需要根据不同的运算构造即可。事实上观察一下这个分治,它和 FFT 的分治写法完全一致,因此我们也可以采用蝴蝶变换的方式递推进行 FWT,复杂度 \(O(n\log n)\)

对于逆变换,和 FFT 类似的,我们构造位矩阵:

\[\begin{bmatrix} c(0,0)& c(0,1)\\ c(1,0)& c(1,1) \end{bmatrix} \]

那么求出位矩阵的逆矩阵,按照原来的变换再做一遍完成了逆变换 IFWT,复杂度还是 \(O(n\log n)\)

2.2 具体运算

2.2.1 按位或

我们构造位矩阵:

\[\begin{bmatrix} 1& 0\\ 1& 1 \end{bmatrix} \]

实际上可以发现这样操作等价于枚举每一个二进制位,把前面的数加到后面的数上去,而这也就是所谓的高维前缀和,这与一般的认识是相符的。

它的逆矩阵如下:

\[\begin{bmatrix} 1& 0\\ -1& 1 \end{bmatrix} \]

也就是高维差分的形式。实现代码的时候可以无脑照搬 FFT, 只需要修改系数即可:

il void FWT(int *a, int n, int o) {
    for(int h = 1; h < n; h <<= 1) {
        for(int i = 0; i < n; i += (h << 1)) {
            for(int j = 0; j < h; j++) {
                if(o == 1) pls(a[i + j + h], a[i + j]);
                else       sub(a[i + j + h], a[i + j]);
            }
        }
    }
}

2.2.2 按位与

把上面的位矩阵反过来,也就是高维后缀和,位矩阵为:

\[\begin{bmatrix} 1& 1\\ 0& 1 \end{bmatrix} \]

逆矩阵为:

\[\begin{bmatrix} 1& -1\\ 0& 1 \end{bmatrix} \]

2.2.3 按位异或

这个运算可能更为关键一点,我们构造位矩阵:

\[\begin{bmatrix} 1& 1\\ 1& -1 \end{bmatrix} \]

它的逆矩阵为:

\[\begin{bmatrix} 0.5& 0.5\\ 0.5& -0.5 \end{bmatrix} \]

而实际上,这里我们可以把 \(\dfrac{1}{2}\) 的系数提出去,则逆矩阵和原矩阵完全一致,区别在于我们需要在最后给整个序列除以 \(n\)

2.3 说明

需要注意的是,FWT 实际上是一种线性变换,所以我们有以下两个结论:

  • \(FWT(A+B)=FWT(A)+FWT(B)\)
  • \(FWT(c\cdot A)=c\cdot FWT(A)\)

同时由于这个特性,我们也可以通过转置原理去求解一些非常规的位运算卷积,例如下式:

\[C_i=\sum_{j}A_jB_{i\cup j} \]

容易发现这个式子的转置就是普通的或卷积,那么将或卷积的过程转置回去就可以得到这个卷积的算法。有一道例题是这个

3 K 进制 FWT

3.1 基本思想

我们上面构造的 \(c\) 数组基于拆位的思想,那么容易想到,如果我们不按照二进制拆分,则实际上可以拓展至 \(K\) 进制 FWT。在这里我们先将三种位运算的定义扩展至 \(K\) 进制:

  • 定义或运算表示在每一位上取较大值。
  • 定义与运算表示在每一位上取较小值。
  • 定义异或运算表示在每一位上做不进位加法。

前两种用处不多,所以我们着重介绍最后一种。

3.2 不进位加法

还是按照 \(c\) 的定义,我们需要构造 \(c(i,j)c(i,k)=c(i,(j+k)\bmod K)\),注意到这里我们取 \(c(i,j)=\omega_K^j\) 就直接对了。但是位矩阵需要有逆矩阵,所以实际上我们可以构造:

\[ \begin{bmatrix} \omega_K^0& \omega_K^0&\omega_K^0&\cdots&\omega_K^0\\ \omega_K^0& \omega_K^1&\omega_K^2&\cdots&\omega_K^{K-1}\\ \omega_K^0& \omega_K^2&\omega_K^4&\cdots&\omega_K^{2(K-1)}\\ \vdots&\vdots&\vdots&\ddots&\vdots\\ \omega_K^0& \omega_K^{K-1}&\omega_K^{2(K-1)}&\cdots&\omega_K^{(K-1)^2}\\ \end{bmatrix} \]

熟悉吗?这就是 FFT 时使用的范德蒙德矩阵,所以它的逆矩阵我们也可以轻松求出,那么我们就完成了 \(K\) 进制下的不进位加法。

现在有一个小问题是 \(K\) 次单位根有可能在模意义下不存在,考虑人为扩域,我们定义 \(x\) 满足 \(x^K=1\) 作为单位根,然后将 \(x\) 代入即可。这么做事实上还有一些问题,不过和本文要讨论的关系就不大了,这里按下不表。

posted @ 2026-03-07 20:48  UKE_Automation  阅读(2)  评论(0)    收藏  举报