模板题:数组
给定$n = 2^k$和两个序列$A_{0..n-1}$, $B_{0..n-1}$,求ide
$$C_i = \sum_{j \oplus k = i} A_j B_k$$函数
其中$\oplus$是某一知足交换律的位运算,要求复杂度$O(nlogn)$。spa
快速沃尔什变换:code
这是什么东西?能吃吗?有用吗? 请参阅SDOI2017r2d1-cut。blog
看到这个你们是否是马上想到了快速傅里叶变换?io
$$C_i = \sum_{j + k = i} A_j B_k$$event
咱们来想一想离散傅里叶变换的本质。模板
$$\begin{aligned}& DFT(A)_i \\
&= A(\omega_n^i)\\
&=\sum_{j=0}^{n-1} A_j * (\omega_n^i)^j\end{aligned}$$class
令$f(n, i, j) = (\omega_n^i)^j$,则
$$DFT(A)_i = \sum_{j=0}^{n-1} A_j f(n, i, j)$$
它要知足$DFT(A)_i * DFT(B)_i = DFT(C)_i$,即
$$(\sum_{j=0}^{n-1} A_j f(n, i, j))(\sum_{k=0}^{n-1} B_k f(n, i, k))=\sum_{l=0}^{n-1} C_l f(n, i, l)$$
$$\sum_{j=0}^{n-1} \sum_{k=0}^{n-1} A_j B_k f(n, i, j) f(n, i, k))=\sum_{l=0}^{n-1} (\sum_{a+b=l} A_a B_b) f(n, i, l)$$
这时咱们发现左右分别有$n^2$项,令对应项系数相等,得
$$f(n, i, j)f(n, i, k) = f(n, i, j + k)$$
只要任意一个能够进行逆变换且知足上述条件的$f$均可以。
如今咱们把上面的$+$都改为$\oplus$,就是离散沃尔什变换即
$$DWT(A)_i = \sum_{j=0}^{n-1} A_j f(n, i, j)$$
$$f(n, i, j)f(n, i, k) = f(n, i, j \oplus k)$$
怎么样,是否是云里雾里顿开茅塞?
然而咱们还须要变快,因此快速傅里叶变换采用
$$f(n, i, j) = (\omega_n^i)^j$$
那它有什么优美的性质呢?
咱们发现, 因为有折半引理,$f(n, i, j)$和$f(n, i+n/2, j)$能够同时从$f(n/2,i,j)$得来。
那么,从感性的角度,既然$\oplus$是一个位运算,那么应该更容易找到一个跟位运算有关的$f$,这样就天然有相似折半引理的东西使得咱们能够作到上述事情。
例如,当$\oplus$是位与时,能够取$f(i, j) = [i \& j = i]$, 即$j$的二进制彻底包含在$i$的二进制里时为1,不然为0。
当$\oplus$是位异或时, 可取$f(i, j) = (-1)^{count(i \& j)}$,其中$count(x)$表示$x$的二进制表示中1的个数。
逆变换:
逆变换看上去好难啊。。。
其实逆变换仍是比较简单的。由于既然$f$跟位运算有关,我就只须要考虑某一位就行了。
例如$\oplus$是位异或时我考虑$n=2,A=(a_0, a_1)$,
那么$DWT(A) = (da_0 = a_0 + a_1, da_1 = a_0 - a_1)$
我只须要解一个二元一次方程(把$da_0, da_1$做为常数, $a_0, a_1$做为变量)就能够解出$a_0, a_1$了。
没了。
关于$f$函数的构造:
$f$函数怎么构造。。。和逆变换的方法差很少啊。。。只须要看$n=2$的状况就行(实际上通常就是$-1$的几回幂,或者$0, 1, -1$)
若是记忆力好能够把全部都背下来,反正知足交换律的位运算只有8个,其中还有2个是全一和全零。。。
把剩下六个列出来吧。。。(下列$f$函数均将第一个参数$n$省略, $[expr]$在布尔表达式$expr$为真时为1, 不然为0)
$\oplus$为位与: $f(i, j) = [j \& i = i]$.
$\oplus$为位或: $f(i, j) = [j \& i = j]$.
$\oplus$为位异或: $f(i, j) = (-1)^{count(i \& j)}$.
$\oplus$为位与非,位或非的时候把三个数组的下标都取反就对应位或和位与。
$\oplus$为同或时直接求位异或卷积再把$C$的下标取反就好了。
吐槽:
明明能够背代码我偏要说这么多。。。
只是由于闲的慌。。。
固然是要帮助你们更好的理解FWT。
至于为何要知足交换律。。。我才不会告诉你我尚未搞出不知足怎么作。
有同窗说FWT难以感性理解。。。我也不知道如何感性理解。。。
代码嘛。。。直接拿FFT改一改就行了。。。
1 void FWT(int *P, int len) { 2 if (len == 1) return; 3 FWT(P, len / 2); 4 FWT(P + len / 2, len / 2); 5 for (int i = 0; i < len / 2; ++i) { 6 int t1 = P[i], t2 = P[i + len / 2]; 7 P[i] = t1 + t2; 8 P[i + len / 2] = t1 - t2; 9 } 10 } 11 void IFWT(int *P, int len) { 12 if (len == 1) return; 13 for (int i = 0; i < len / 2; ++i) { 14 int t1 = P[i], t2 = P[i + len / 2]; 15 P[i] = (t1 + t2) / 2; 16 P[i + len / 2] = (t1 - t2) / 2; 17 } 18 IFWT(P, len / 2); 19 IFWT(P + len / 2, len / 2); 20 }