快速沃尔什变换 (FastWalsh−HadamardTransform, 简称 FWT),是一种可以加速集合卷积的算法
会 FFT的同学肯定知道,FFT加速多项式乘法实际上就是加速了一个卷积。在 FFT里,卷积的形式是长这个样子的:
C(k)=∑i+j=kA(i)B(j)
而所谓集合卷积又是一个怎样的东西呢?其实它就是长这个样子的
C(k)=∑i×j=kA(i)B(j)
其中 ×指的是一个集合运算符
那 FWT这东西具体是怎么操作的呢?
让我们来首先回忆一下 FFT是怎样操作的:
1. 利用 dft将多项式变为点值表示(运用分治思想)
2. 将点值相乘
3. 利用 idft将点值变回原来的系数形式
现在我来说一下 FWT的思路,其实和 FFT是十分相似的:
1. 利用 FWT变换将原数组变为点值表示(也要分治)
2. 将点值相乘
3. 利用 UFWT变换将点值变回来
这样说似乎有点抽象,总结来说就是将原来的卷积运算变为点值运算,这里我们定义 ⋅为点乘运算符,效果是将两个数组对应位相乘,复杂度是 Θ(n)的,因此我们要寻找一组变换及其逆运算 FWT()和 UFWT(),使得它满足 FWT(A)⋅FWT(B)=FWT(C)且 A′=FWT(A)A=UFWT(A′)
而我们现在的核心问题就是找到这个运算,比较 exciting的是,对于每种运算,它的 FWT变换往往都有一定的差别(但差别不是特别大)我们先从或运算 ′|′开始了解这个神秘的变换
首先或运算的 FWT是长这样的:
A′[i]=∑i|j=iA[j]
我们该如何理解这个式子呢?其实我们可以把 i,j都看作一个集合(由二进制数表示),接着我们会发现上面运算的实质就是将 i 中的所有子集求和,我们会发现通过这种变换后 A′⋅B′=C′这个式子是成立的,这里给出证明:
设 i为定义域内任意数,则上式变为
A′[i]⋅B′[i]=C′[i]
接着将式子展开:
(∑i|j=iA[j])⋅(∑i|j=iB[j])=∑i|j=iC[j]
再将右边展开:
(∑i|j=iA[j])⋅(∑i|j=iB[j])=∑i|j=i∑k1|k2=jA[k1]B[k2]
考虑换元,即:
i|j=ii|k1|k2=i
即 k1和 k2也是 i的子集,于是原式化为:
(∑i|j=iA[j])⋅(∑i|j=iB[j])=∑i|k1=i∑i|k2=jA[k1]B[k2]
易知左右两边是相等的
嗯那我们既然已经证明了这个,那现在的问题是如何由 A快速通过 FWT求得 A′
我会枚举子集
那你这个时间复杂度就退化成原来的啦!
考虑类似 FFT里面的分治思想。
为了方便分治,我们假设 A′数组的长度是 2k的(不足的位数在前面补上 0,和 FFT差不多的),接着我们将 A′分为两半,设 A′={a1,a2,⋯,a2k},那么 A′1={a2k−1+1,a2k−1+2,⋯,a2k},A′0={a1,a2,⋯,a2k−1}
假设我们已经知道了 A0′,A′1,我们如何求 A′呢?
考虑我们当前求的 A′的下标是 k,则 A0′,A′1的唯一的不同就在当前最高位,A′0这一位是 0,A′1这一位是 1,我们可以发现,如果这一位是 0的话,根据或的性质,只有 A′0是它的子集(最高位只能填 0呀),如果这一位是 1的话,那么 A1′,A′0都是它的子集(最高位随便填呀),因此我们便得到一个这样的函数:
A′=merge(A0′,A′0+A1′)
其中 merge(x,y)能将 x,y两个数组拼起来,+指的就是按数组下标相加喽。
因此我们就能够用 Θ(nlogn)的复杂度求出 A′了
那逆变换怎么搞呢?
其实我们可以根据刚才推出来的东西倒着推回去,我们知道:
A′0=A0,A′1=A0+A1
那么很容易可以推出:
A0=A0′,A1=A1′−A′0
接着用类似上面的方法就能推回去啦~
(感觉自己还是讲的好玄乎呀 QAQ)
对于与运算,异或运算,我们也可以用类似的方法推一遍,这里给出它们最后的公式:
与运算:
A′=merge(A′0+A1′,A1′)
A=merge(A0−A1,A1)
异或运算:
A′=merge(A′0+A1′,A0′−A1′)
A=merge(A0+A12,A0−A12)
与运算跟或运算的推导过程差不多,异或运算的推导我还有点昏(捂脸)
QAQ 蒟蒻的我也只能记一下公式了。
1 条评论
konnyakuxzy · 2018年4月19日 12:27 下午
Orz 最近没有时间看
等后面脱离常规的时候再看吧 QvQ