最近在學FWT,抽點時間出來把這個算法總結一下。
快速沃爾什變換(Fast Walsh-Hadamard Transform),簡稱FWT。是快速完成集合卷積運算的一種算法。
主要功能是求:,其中
為集合運算符。
就像FFT一樣,FWT是對數組的一種變換,我們稱數組X的變換為FWT(X)。
所以FWT的核心思想是:
為了求得C=A★B,我們瞎搞搞出一個變換FWT(X),
使得FWT(C)=FWT(A) FWT(B),然后根據FWT(C)求得C。
(其中★表示卷積運算,表示將數組對應下標的數相乘的運算)
也就是說我們可以通過FWT(X)變換把復雜度O(n^2)的★運算變為O(n)的運算。
跟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的二進制(到第k位為止)中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個鬼啊(╯‵□′)╯︵┻━┻!這個做法槽點真的太多了好嗎?
不過能夠得出正確答案是真的。