1. 程式人生 > >一個數學不好的菜雞的快速沃爾什變換(FWT)學習筆記

一個數學不好的菜雞的快速沃爾什變換(FWT)學習筆記

spa 最長 ML form 相加 pre 證明 a* orm

一個數學不好的菜雞的快速沃爾什變換(FWT)學習筆記

曾經某個下午我以為我會了FWT,結果現在一丁點也想不起來了……看來“學”完新東西不經常做題不寫博客,就白學了 = =

我沒啥智商 ,網上的FWT博客我大多看不懂,下面這篇博客是留給我我再次忘記FWT時看的,所以像我一樣的沒智商選手應該也能看懂!有智商選手更能看懂咯!

(寫得非常匆忙,如有任何錯誤請在評論區指正!TAT)

什麽是FWT

FWT是用來快速做位運算卷積的。位運算卷積是什麽?給出兩個數組\(A\)\(B\)(長度相等且是2的整數次冪),求一個數組\(C\),滿足\(A * B = C\),這個“\(*\)”的定義如下:\[A * B = C \Leftrightarrow C_k = \sum_{i\oplus j = k}A_i \cdot B_j\]

其中“\(\oplus\)”是一種位運算,可以是與(&)、或(|)、異或(^)。

為什麽要有一個變換呢?回想一下FFT,FFT求\(A*B\)時(這個“\(*\)”是多項式乘法那個卷積),是把\(A\)\(B\)各自“變換”了一下,然後把變換後的數組按位相乘,得到“變換後的\(C\)”——\(tf(C)\),然後把\(tf(C)\)逆變換回去,得到\(C\)數組。

FWT做位運算卷積的原理也類似,想要實現快速位運算卷積,就要找到一種變換\(tf\)滿足\(tf(A*B) = tf(A)\times tf(B)\),這裏的“\(\times\)”表示兩個數組按位相乘(和那個表示卷積的“\(*\)

”不是一個符號)。

再強調一下本文中符號的定義,在下文中:

\[A * B = C \Leftrightarrow C_k = \sum_{i\oplus j = k}A_i \cdot B_j\]
\[A \times B = C \Leftrightarrow C_i = A_i \cdot B_i\]

用FWT解決或卷積

或卷積,就是把\(A * B = C \Leftrightarrow C_k = \sum_{i\oplus j = k}A_i \cdot B_j\)中的“\(\oplus\)”定義為按位或運算(|)。我們的目標是找到一種變換\(tf\)滿足\(tf(A*B) = tf(A)\times tf(B)\)

,還要找到一種逆變換\(utf\),能把\(tf(C)\)變回\(C\)

目標

  • 找到\(tf\)
  • 找到\(utf\)

找到\(tf\)

這是位運算,所以應該按位分治。

根據下標在最高位是0還是1,把\(A\)數組拆成兩個數組\(A_0\)\(A_1\)\(A_0\)\(A\)中下標最高位是0的元素組成的數組,\(A_1\)\(A\)中下標最高位是1的元素組成的數組——實際上,\(A_0\)就是\(A\)的前一半,\(A_1\)\(A\)的後一半。用\(A = (A_0, A_1)\)表示這種“等式右邊兩個數組首尾相接就能得到等式左邊的數組”的關系。

定義\[tf(A) = (tf(A_0), tf(A_1) + tf(A_0))\]

\(A\)長度為1,無法再劃分時,\(tf(A) = A\)

對了,顯然\(tf(X + Y) = tf(X) + tf(Y)\),這裏“\(+\)”就是按位相加。

(這個\(tf\)是怎麽找到的?這篇博客講了講……但是即使我知道了如何找到或卷積的\(tf\),異或卷積的我還是找不出來……還是甩出這個式子然後證明它吧。)

來證明一下\(tf(C = A * B) = tf(A) \times tf(B)\)

\(A, B\)長度均為1時顯然。

\(A, B\)長度大於1時 ,我們使用歸納法——可以假定“長度除以2\(tf(C = A * B) = tf(A) \times tf(B)\)是成立的”,即\[tf(A_0*B_0) = tf(A_0) \times tf(B_0)\\tf(A_1 * B_1) = tf(A_1) \times tf(B_1)\\tf(A_0 * B_1) = tf(A_0) \times tf(B_1)\\tf(A_1 * B_0) = tf(A_1) \times tf(B_0)\]如果我們在這四個條件的基礎上能證明\(tf(C = A * B) = tf(A) \times tf(B)\),則這四個條件遞歸證明即可,遞歸到長度為1時,就直接證畢了。

那麽如何證明當前這一層\(tf(C = A * B) = tf(A) \times tf(B)\)呢?

首先,\[C=(A_0 * B_0, A_1 * B_0 + A_0 * B_1 + A_1 * B_1)\]。這是可以理解的:在\(A\)中最高位是0的一個下標,和在\(B\)中最高位是0的一個下標,或起來還是0,所以他倆卷積的結果應該放在\(C_0\)中,其余三項同理。

然後從等式左邊推一下,\[\begin{align*}tf(C) &= (tf(A_0 * B_0), tf(A_1 * B_0 + A_0 * B_1 + A_1 * B_1) + tf(A_0 * B_0))\\&=(tf(A_0*B_0), tf(A_1*B_0) + tf(A_0*B_1) + tf(A_1 * B_1) + tf(A_0 * B_0)) \\ &= (tf(A_0) \times tf(B_0), tf(A_1) \times tf(B_0) + tf(A_0) \times tf(B_1) + tf(A_1) \times tf(B_1) + tf(A_0)\times tf(B_0))\end{align*}\]

這一步是基於\(tf\)的定義以及上面的那四個條件的。

然後從等式右邊推一下,\[\begin{align*}tf(A) \times tf(B) &= (tf(A_0), tf(A_1) + tf(A_0)) \times (tf(B_0), tf(B_1) + tf(B_0)))\\&=(tf(A_0) \times tf(B_0), tf(A_0)\times tf(B_0) + tf(A_1) \times tf(B_0) + tf(A_0) \times tf(B_1) + tf(A_1) \times tf(B_1))\end{align*}\]

這一步是基於“\(\times\)”符號的意義——按位相乘得出來的。

這樣一來,等式兩邊恰好相等誒!

所以我們已經找到了或卷積的\(tf\)\[tf(A) = (tf(A_0), tf(A_1) + tf(A_0))\]

找到\(utf\)

目標:找到一個\(utf\)使得\(utf(tf(A)) = A\)

這相當於把上面那個式子倒著推,怎麽個倒推法呢?

正著推是已知\(A = (A_0, A_1)\),求\(tf(A) = (tf(A)_0, tf(A)_1)\)

倒著推就是已知\(tf(A) = (tf(A)_0, tf(A)_1)\),求\(utf(tf(A)) = A = (A_0, A_1) = (utf(tf(A_0)), utf(tf(A_1)))\)

那麽根據上面的\(tf(A) = (tf(A_0), tf(A_1) + tf(A_0))\),有\(tf(A)_0 = tf(A_0), tf(A)_1 = tf(A_0) + tf(A_1)\)

所以直接得到\(tf(A_0) = tf(A)_0\), 兩式相減又得到\(tf(A_1) = tf(A)_1 - tf(A)_0\)

所以\(utf(tf(A)) = A = (A_0, A_1) = (utf(tf(A_0)), utf(tf(A_1)) = (utf(tf(A)_0), utf(tf(A)_1 - tf(A)_0))\)

\(tf(A)\)替換成\(A\),得到\(utf(A) = (utf(A), utf(A_1) - utf(A_0))\)

這就是逆變換\(utf\)了。

總結

或卷積的FWT:

\[tf(A) = (tf(A_0), tf(A_1) + tf(A_0))\]
\[utf(A) = (utf(A), utf(A_1) - utf(A_0))\]

用FWT解決與卷積

與卷積和或卷積非常類似。

\(C = (A_0*B_0 + A_0*B_1 + A_1 *B_0, A_1*B_1)\)

定義\[tf(A) = (tf(A_0) + tf(A_1), tf(A_1))\]

類似上面或卷積的證明過程可以證明它。

類似地,\[utf(A) = (utf(A_0) - utf(A_1), utf(A_1))\]

用FWT解決異或卷積

和上面的也很類似,但是異或卷積的式子更復雜一丁點。它是:

\[tf(A) = (tf(A_0) + tf(A_1), tf(A_0) - tf(A_1))\]
\[utf(A) = (\frac{utf(A_0) + utf(A_1)}{2}, \frac{utf(A_0) - utf(A_1)}{2})\]

證明嘛……和上面的或卷積證明也差不多!

板子

我的異或卷積板子:

ll inc(ll x, ll y){return (x += y) >= P ? x - P : x;}
ll dec(ll x, ll y){return (x -= y) < 0 ? x + P : x;}
void transform(ll *a, int n, bool inv){
    for(int l = 2; l <= n; l <<= 1){
    int m = l >> 1;
    for(ll *p = a; p != a + n; p += l)
        for(int i = 0; i < m; i++){
        ll t = p[i + m];
        p[i + m] = dec(p[i], t);
        p[i] = inc(p[i], t);
        }
    if(inv)
        for(int i = 0; i < n; i++)
        a[i] = a[i] * inv2 % P;
    }
}

異或已經是寫起來最長的啦,其他兩個都特別短~

一個數學不好的菜雞的快速沃爾什變換(FWT)學習筆記