最近在学FWT,抽点时间出来把这个算法总结一下。算法
快速沃尔什变换(Fast Walsh-Hadamard Transform),简称FWT。是快速完成集合卷积运算的一种算法。数组
主要功能是求:,其中
为集合运算符。spa
就像FFT同样,FWT是对数组的一种变换,咱们称数组X的变换为FWT(X)。3d
因此FWT的核心思想是:orm
为了求得C=A★B,咱们瞎搞搞出一个变换FWT(X),blog
使得FWT(C)=FWT(A) FWT(B),而后根据FWT(C)求得C。io
(其中★表示卷积运算,表示将数组对应下标的数相乘的运算)ast
也就是说咱们能够经过FWT(X)变换把复杂度O(n^2)的★运算变为O(n)的运算。form
跟FFT是彻底相同的。因此咱们考虑怎么搞出这个FWT(X)。二进制
与运算(&)和或运算(|)最容易理解,咱们先讲讲这两个怎么搞。咱们以或运算为例。
若 x | y = z,那么x和y必定知足二进制中的1为z的子集。
因此咱们能够令FWT(A)=A',其中。(“i | j = i”就是表示“j知足二进制中的1为i的子集”)
(虽然以上两行根本构不成逻辑,可是请相信这个定义就是正确的)
因而咱们就有C'=A' B',从这些数组的定义来看,这个式子确实是正确的。
而后咱们就要研究一下FWT(A)也就是A'怎么求。固然不能枚举 i 的子集,这样复杂度过高。
既然是二进制的,咱们不妨考虑用分治进行计算。(为毛是二进制就要用分治啊喂!)
咱们设A0为A的前一半,A1为A的后一半,若是A的长度为2^k,那么A0、A1的长度为2^(k-1)。
若是咱们知道了FWT(A0)和FWT(A1),那么FWT(A)是否是就能够求了呢?固然能够。
FWT(A)的前一半至关于二进制位的第k位填了0,那么是它子集的仍然只有FWT(A0) (它自己);
FWT(A)的后一半至关于二进制位的第k位填了1,那么是它子集的不只仅有FWT(A1) (它自己),还有FWT(A0)。
那么转移就出来了,。
其中merge(X,Y)为X和Y两个数组接在一块儿, 表示将数组对应下标的数相加的运算。
这样咱们就在O(2^k*k)也就是O(nlogn)的时间内求出了FWT(A)。
还有一个问题,咱们怎么经过FWT(C)求得C?
这不就是FWT()的逆变换吗?根据转移方程,你难道不会倒着推回来吗?
这其实就至关于你知道了A0'、A1',而后要求得A0、A1。
由于咱们已经知道了A0'=A0,A1'=A1+A0,因此咱们就有A0=A0',A1=A1'-A0'。
因此:
。
既然知道了或运算,与运算也是同样的,由于与运算和或运算本质是相同的。
读者能够试着本身推一遍,再继续往下看:
而后就是鬼畜的异或,先说结论:
一种比较靠谱的说法是,其中d(x)为x的二进制中1的个数。
先想想再往下看吧~
而后你可能会想,与和或本质上不是相同的吗?
而后就有,其中d'(x)为x的二进制中0的个数。
而后就有,
。
而后结果是一毛同样的。
(其实就是把整个数组倒过来而已)
因此又回到FWT的核心思想:
为了求得C=A★B,咱们瞎搞搞出一个变换FWT(X),
使得FWT(C)=FWT(A) FWT(B),而后根据FWT(C)求得C。
因此咱们只要找到运算中,数字x的一个特征FWT(x),知足FWT(x
y)=FWT(x)*FWT(y)。这个特征就是FWT啦~
怎么样,是否是给你一种“我上我也行”的感受?
而后你试图寻求异或FWT的更简单的公式:
而后你开始脑补:。
而后惊喜地发现正好知足 FWT(C)=FWT(A) FWT(B) !
而后你开始写转移公式:
(以上18行都是小C在口胡)
到底有没有靠谱的作法呢?小D说他有一种解方程的作法。
为了避免失通常性,假设A、B、C数组的长度为2。C=A★B,为异或卷积运算。
而后显然C[0] = A[0]*B[0] + A[1]*B[1] , C[1] = A[0]*B[1] + A[1]*B[0]。
而后咱们开始变形了!因为(咱们猜测)FWT(A)必定是a*FWT(A0) + b*FWT(A1)的形式,因此:
设A'[0] = a*A[0] + b*A[1],A'[1] = c*A[0] + d*A[1]。
B和C同理,由于小C知道列一大堆方程出来确定没人看因此小C就不列出来了。
而后就有C'[0] = A'[0]*B'[0]。解方程,得:
由于a,b不一样时等于0,因此a=1,b=±1。同理,c=1,d=±1。
(因为解方程过程当中有不少槽点因此小C也不能保证正确性)
而后到底是+1仍是-1呢?小D给的说法是“枚举,check一下”。
check个鬼啊(╯‵□′)╯︵┻━┻!这个作法槽点真的太多了好吗?
不过可以得出正确答案是真的。