最近在學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個鬼啊(╯‵□′)╯︵┻━┻!這個作法槽點真的太多了好嗎?
不過可以得出正確答案是真的。