Loading [MathJax]/jax/output/HTML-CSS/jax.js

快速沃尔什变换 (FastWalshHadamardTransform, 简称 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 中的所有子集求和,我们会发现通过这种变换后 AB=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=ik1|k2=jA[k1]B[k2]

考虑换元,即:
i|j=ii|k1|k2=i
k1k2也是 i的子集,于是原式化为:
(i|j=iA[j])(i|j=iB[j])=i|k1=ii|k2=jA[k1]B[k2]
易知左右两边是相等的

嗯那我们既然已经证明了这个,那现在的问题是如何由 A快速通过 FWT求得 A

我会枚举子集

那你这个时间复杂度就退化成原来的啦!

考虑类似 FFT里面的分治思想。

为了方便分治,我们假设 A数组的长度是 2k的(不足的位数在前面补上 0,和 FFT差不多的),接着我们将 A分为两半,设 A={a1,a2,,a2k},那么 A1={a2k1+1,a2k1+2,,a2k}A0={a1,a2,,a2k1}

假设我们已经知道了 A0,A1,我们如何求 A呢?

考虑我们当前求的 A的下标是 k,则 A0,A1的唯一的不同就在当前最高位,A0这一位是 0,A1这一位是 1,我们可以发现,如果这一位是 0的话,根据或的性质,只有 A0是它的子集(最高位只能填 0呀),如果这一位是 1的话,那么 A1,A0都是它的子集(最高位随便填呀),因此我们便得到一个这样的函数:
A=merge(A0,A0+A1)
其中 merge(x,y)能将 x,y两个数组拼起来,+指的就是按数组下标相加喽。

因此我们就能够用 Θ(nlogn)的复杂度求出 A

那逆变换怎么搞呢?

其实我们可以根据刚才推出来的东西倒着推回去,我们知道:
A0=A0,A1=A0+A1
那么很容易可以推出:
A0=A0,A1=A1A0

接着用类似上面的方法就能推回去啦~

(感觉自己还是讲的好玄乎呀 QAQ)

对于与运算,异或运算,我们也可以用类似的方法推一遍,这里给出它们最后的公式:

与运算:
A=merge(A0+A1,A1)
A=merge(A0A1,A1)

异或运算:
A=merge(A0+A1,A0A1)
A=merge(A0+A12,A0A12)

与运算跟或运算的推导过程差不多,异或运算的推导我还有点昏(捂脸)

QAQ 蒟蒻的我也只能记一下公式了。

分类: 文章

HomuraCat

明日は何になる? やがて君になる!

1 条评论

konnyakuxzy · 2018年4月19日 12:27 下午

Orz 最近没有时间看
等后面脱离常规的时候再看吧 QvQ

回复 konnyakuxzy 取消回复

Avatar placeholder

您的邮箱地址不会被公开。 必填项已用 * 标注