快速沃尔什变换 FWT
1 定义
我们曾经学习过多项式乘法,其核心在于快速傅里叶变换 FFT。而本质上,多项式乘法实际上就是在对下标做加法卷积,如果要对下标做位运算卷积,则我们就需要引入快速沃尔什变换 FWT。
形式化的,给出两个序列 \(A_i,B_i\),定义位运算卷积为:
其中 \(\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 后序列对位相乘的要求,可以得出一个关键结论:
并且 \(c\) 函数的一个重要性质是他可以按位处理,也就是说对于 \(c(i,j)\),我们可以把它的二进制位拆出来,然后令 \(c(i,j)=\prod c(i_k,j_k)\),显然如果在每一位都满足上面的性质,那么乘起来之后也是完全正确的。
那么此时我们 \(c\) 两维的值域就变成了 \(0/1\),我们只需要构造出 \(c\) 似乎就可以解决问题了。
我们可以将上面 FWT 的定义式进行变形:
考察后面的区别,发现只有最高位是不一样的,把 \(i,j\) 的最高位提出来,设去掉最高位后的 \(i,j\) 为 \(i',j'\),则有:
那么此时左右两边的变换就是完全一致的了,我们只需要根据 \(c\) 的值进行合并,那么我们就拥有了一个分治求解 FWT 的方法,而具体的 \(c\) 只需要根据不同的运算构造即可。事实上观察一下这个分治,它和 FFT 的分治写法完全一致,因此我们也可以采用蝴蝶变换的方式递推进行 FWT,复杂度 \(O(n\log n)\)。
对于逆变换,和 FFT 类似的,我们构造位矩阵:
那么求出位矩阵的逆矩阵,按照原来的变换再做一遍完成了逆变换 IFWT,复杂度还是 \(O(n\log n)\)。
2.2 具体运算
2.2.1 按位或
我们构造位矩阵:
实际上可以发现这样操作等价于枚举每一个二进制位,把前面的数加到后面的数上去,而这也就是所谓的高维前缀和,这与一般的认识是相符的。
它的逆矩阵如下:
也就是高维差分的形式。实现代码的时候可以无脑照搬 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 按位与
把上面的位矩阵反过来,也就是高维后缀和,位矩阵为:
逆矩阵为:
2.2.3 按位异或
这个运算可能更为关键一点,我们构造位矩阵:
它的逆矩阵为:
而实际上,这里我们可以把 \(\dfrac{1}{2}\) 的系数提出去,则逆矩阵和原矩阵完全一致,区别在于我们需要在最后给整个序列除以 \(n\)。
2.3 说明
需要注意的是,FWT 实际上是一种线性变换,所以我们有以下两个结论:
- \(FWT(A+B)=FWT(A)+FWT(B)\)。
- \(FWT(c\cdot A)=c\cdot FWT(A)\)。
同时由于这个特性,我们也可以通过转置原理去求解一些非常规的位运算卷积,例如下式:
容易发现这个式子的转置就是普通的或卷积,那么将或卷积的过程转置回去就可以得到这个卷积的算法。有一道例题是这个。
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\) 就直接对了。但是位矩阵需要有逆矩阵,所以实际上我们可以构造:
熟悉吗?这就是 FFT 时使用的范德蒙德矩阵,所以它的逆矩阵我们也可以轻松求出,那么我们就完成了 \(K\) 进制下的不进位加法。
现在有一个小问题是 \(K\) 次单位根有可能在模意义下不存在,考虑人为扩域,我们定义 \(x\) 满足 \(x^K=1\) 作为单位根,然后将 \(x\) 代入即可。这么做事实上还有一些问题,不过和本文要讨论的关系就不大了,这里按下不表。

浙公网安备 33010602011771号