你可能会问为什么这里只有 FWT,因为 FFT 和 NTT 我懒得写,好了,FFT 和 NTT 在写,2~3 天后补上。
快速沃尔什变换(FWT)
变换内容
快速沃尔什变换(下称 FWT)通常用来解决的问题形如
\[\begin{align}
C_k = \sum_{i \otimes j = k} A_i B_j
\end{align}
\]
其中,\(\otimes\) 是一种二元运算。与 FFT 中的加法不同,\(\otimes\) 一般是位运算,它可以按位计算。
显然,对于数组(向量)\(X\),我们需要构造 \(\text{FWT}(X)\)(它也是一个数组),使得对于任意的 \(i\),满足
\[\begin{align}
\text{FWT}(C)_i = \text{FWT}(A)_i \cdot \text{FWT}(B)_i
\end{align}
\]
这样,我们可以通过求出 \(\text{FWT}(A)\) 和 \(\text{FWT}(B)\) 来求出 \(\text{FWT}(C)\),再通过逆 FWT 来求出 \(C\)。
那么,如何构造 \(\text{FWT}(X)\) 呢?一种显然的构造是
\[\begin{align}
\text{FWT}(X)_i = \sum_{j} c_{i, j} X_j
\end{align}
\]
上述构造意在通过 \(c_{i, j} (c_{i, j} = 0 / 1)\) 来建立起来 \(i\) 和 \(j\) 的联系,我们将它带回 \((2)\) 中,这样,对于任意的 \(i\),有
\[\begin{align}
\sum_{j} c_{i, j} C_j = (\sum_{j} c_{i, j} A_j) \cdot (\sum_{j} c_{i, j} B_j)
\end{align}
\]
进一步地,有
\[\begin{align}
\sum_{j} c_{i, j} C_j = \sum_{k, l} c_{i, k} c_{i, l} A_k B_l
\end{align}
\]
由 \((1)\),有
\[\begin{align}
\begin{aligned}
\sum_{j} c_{i, j} C_j &= \sum_{j} c_{i, j} \sum_{k \otimes l = j} A_k B_l \\
&= \sum_{j} \sum_{k \otimes l = j} c_{i, k \otimes l} A_k B_l \\
&= \sum_{k, l} c_{i, k \otimes l} A_k B_l
\end{aligned}
\end{align}
\]
与 \((5)\) 联立,容易发现可以有
\[\begin{align}
c_{i, k} c_{i, l} = c_{i, k \otimes l}
\end{align}
\]
好的,现在我们发现了 \(c\) 的一个重要性质!在后面将会用到。
下面,我们开始求 \(\text{FWT}(X)_i\)。设 \(X\) 的长度为 \(n\),记 \(x_{(j)}\) 表示 \(x\) 在二进制下从低往高第 \(j\) 位的值,设 \(k\) 为 \(n\) 在二进制下的最高位,记 \(i ^ \prime, j ^ \prime\) 表示分别去掉 \(i,j\) 的第 \(k\) 位后的值。根据 \((3)\),将 \(\text{FWT}(X)_i\) 分成两半,有
\[\begin{align}
\begin{aligned}
\text{FWT}(X)_i &= \sum_{j} c_{i, j} X_j \\
&= \sum_{j = 0}^{(n / 2) - 1} c_{i,j}X_j + \sum_{j = (n / 2)}^{n - 1} c_{i,j}X_j
\end{aligned}
\end{align}
\]
分开考虑它们下标(就是 \(j\))的(二进制)第 \(k\) 位,显然,前半部分的是 \(0\),后半部分的是 \(1\),再根据 \(\otimes\) 的性质和 \((7)\),有
\[\begin{align}
\begin{aligned}
\text{FWT}(X)_i &= \sum_{j = 0}^{(n / 2) - 1} c_{i,j}X_j + \sum_{j = (n / 2)}^{n - 1} c_{i,j}X_j \\
&= c_{i_{(k)}, 0} \sum_{j = 0}^{(n / 2) - 1} c_{i^\prime,j^\prime}X_j + c_{i_{(k)}, 1}\sum_{j = (n / 2)}^{n - 1} c_{i ^ \prime,j ^ \prime}X_j
\end{aligned}
\end{align}
\]
这里,对于 \((n / 2) \leq j < n\),有 \(c_{i ^ \prime, j ^ \prime} = c_{i, j}\),而且对于 \(0 \leq j < (n / 2)\),有 \(j ^ \prime = j\),那么有
\[\begin{align}
\begin{aligned}
\text{FWT}(X)_i &= c_{i_{(k)}, 0} \sum_{j = 0}^{(n / 2) - 1} c_{i^\prime,j^\prime}X_j + c_{i_{(k)}, 1}\sum_{j = (n / 2)}^{n - 1} c_{i ^ \prime,j ^ \prime}X_j \\
&= c_{i_{(k)}, 0} \sum_{j = 0}^{(n / 2) - 1} c_{i^\prime,j}X_j + c_{i_{(k)}, 1}\sum_{j = (n / 2)}^{n - 1} c_{i,j}X_j
\end{aligned}
\end{align}
\]
其实,两个 \(\sum\) 每个都可以看作一个子问题。记 \(X_0\) 为 \(X\) 中下标在 \(0 \sim (n / 2) - 1\) 范围内的数成的数组,\(X_1\) 表是 \(X\) 中下标在 \((n / 2) \sim n - 1\) 范围内的数成的数组(在 \(X_1\) 中起始位置为 \(0\))。这样分割开 \(X\) 后,对于在 \(X\) 中的下标 \(i\),它在 \(X_1\) 中的位置为 \(i - (n / 2)\),即 \(i \gets i ^ \prime\),那么有
\[\begin{align}
\begin{aligned}
\text{FWT}(X)_i &= c_{i_{(k)}, 0} \sum_{j = 0}^{(n / 2) - 1} c_{i^\prime,j}X_j + c_{i_{(k)}, 1}\sum_{j = (n / 2)}^{n - 1} c_{i,j}X_j \\
&= c_{i_{(k)}, 0} \cdot \text{FWT}(X_0)_{i ^ \prime} + c_{i_{(k)}, 1} \cdot \text{FWT}(X_1)_{i} \\
&= c_{i_{(k)}, 0} \cdot \text{FWT}(X_0)_{i ^ \prime} + c_{i_{(k)}, 1} \cdot \text{FWT}(X_1)_{i ^ \prime}
\end{aligned}
\end{align}
\]
下面我们将对 \(i_{(k)}\) 分类讨论。若 \(i_{(k)} = 0\),有
\[\begin{align}
\text{FWT}(X)_i = c_{0, 0} \cdot \text{FWT}(X_0)_{i ^ \prime} + c_{0, 1} \cdot \text{FWT}(X_1)_{i ^ \prime}
\end{align}
\]
否则,有
\[\begin{align}
\text{FWT}(X)_i = c_{1, 0} \cdot \text{FWT}(X_0)_{i ^ \prime} + c_{1, 1} \cdot \text{FWT}(X_1)_{i ^ \prime}
\end{align}
\]
尝试去掉 \(i\)。记 \(\text{FWT}(X)\) 的前半部分(即 \(i_{(k)} = 0\) 的部分)记为 \(\text{FWT}_{0}(X)\),后半部分记为 \(\text{FWT}_{1}(X)\),那么 \(\text{FWT}(X)\) 为它们首尾拼接起来,此时有
\[\begin{align}
\text{FWT}_{0}(X) = c_{0, 0} \cdot \text{FWT}(X_0) + c_{0, 1} \cdot \text{FWT}(X_1)
\end{align}
\]
和
\[\begin{align}
\text{FWT}_{1}(X) = c_{1, 0} \cdot \text{FWT}(X_0) + c_{1, 1} \cdot \text{FWT}(X_1)
\end{align}
\]
这里的加号表示数组对位相加,乘号表示数组中所有数都乘上一个数,下同。
综合以上,我们成功地将 \(\text{FWT}(X)\) 分拆成了两个大小相同的子问题。这样,我们可以通过分治在 \(O(n \log n)\) 的时间复杂度内求出 \(\text{FWT}(X)\)。
等等!还没完!我们还只是通过 \((2)\) 求出了 \(\text{FWT}(C)\),那么如何求出 \(C\) 呢?下面我们将介绍逆 FWT。
记 \(\text{IFWT}(X)\) 表示逆 FWT(\(\text{IFWT}_{0/1}(X)\) 的定义同 \(\text{FWT}_{0/1}(X)\)),对于数 \(a\) 和 \(b\),\(\text{IFWT}(X)\) 和 \(\text{FWT}(X)\) 有性质
\[\begin{align}
\text{IFWT}(\text{FWT}(X)) = \text{FWT}(\text{IFWT}(X))
\end{align}
\]
和
\[\begin{align}
\text{FWT}(aX+bY) = a \cdot \text{FWT}(X) + b \cdot \text{FWT}(Y)
\end{align}
\]
这两个性质都可以用 \((3)\) 的定义式来证明,结合它们,有
\[\begin{align}
\text{IFWT}(aX+bY) = a \cdot \text{IFWT}(aX) + b \cdot \text{IFWT}(bY)
\end{align}
\]
仔细观察 \((14)\) 和 \((15)\),你会发现这酷似一个矩阵乘法的式子,\(\text{FWT}(X_0)\) 在矩阵中可以看做一个行向量,有
\[\begin{align}
\begin{bmatrix}
c_{0, 0} & c_{0, 1} \\
c_{1, 0} & c_{1, 1}
\end{bmatrix}
\times
\begin{bmatrix}
\text{FWT}(X_0) \\
\text{FWT}(X_1)
\end{bmatrix}
=
\begin{bmatrix}
\text{FWT}_{0}(X) \\
\text{FWT}_{1}(X)
\end{bmatrix}
\end{align}
\]
我们先记录下它左边的式子,有
\[\begin{align}
T = \begin{bmatrix}
c_{0, 0} & c_{0, 1} \\
c_{1, 0} & c_{1, 1}
\end{bmatrix}
\end{align}
\]
记 \(T ^ {-1}\) 为 \(T\) 的逆矩阵,那么,如果 \((16)\) 两边同时左乘 \(T ^ {-1}\),有
\[\begin{align}
\begin{bmatrix}
\text{FWT}(X_0) \\
\text{FWT}(X_1)
\end{bmatrix}
=
T^{-1} \times \begin{bmatrix}
\text{FWT}_{0}(X) \\
\text{FWT}_{1}(X)
\end{bmatrix}
\end{align}
\]
记
\[\begin{align}
T^{-1} = \begin{bmatrix}
t_{0, 0}, t_{0, 1} \\
t_{1, 0}, t_{1, 1}
\end{bmatrix}
\end{align}
\]
回到 \((21)\),令 \(X = \text{IFWT}(X)\),有
\[\begin{align}
\begin{bmatrix}
\text{FWT}(\text{IFWT}_0(X)) \\
\text{FWT}(\text{IFWT}_1(X))
\end{bmatrix}
=
\begin{bmatrix}
t_{0, 0}, t_{0, 1} \\
t_{1, 0}, t_{1, 1}
\end{bmatrix} \times \begin{bmatrix}
\text{FWT}_{0}(\text{IFWT}(X)) \\
\text{FWT}_{1}(\text{IFWT}(X))
\end{bmatrix}
\end{align}
\]
即
\[\begin{align}
\begin{bmatrix}
\text{FWT}(\text{IFWT}_0(X)) \\
\text{FWT}(\text{IFWT}_1(X))
\end{bmatrix}
=
\begin{bmatrix}
t_{0, 0}, t_{0, 1} \\
t_{1, 0}, t_{1, 1}
\end{bmatrix} \times \begin{bmatrix}
X_0 \\
X_1
\end{bmatrix}
\end{align}
\]
那么有
\[\begin{align}
\text{FWT}(\text{IFWT}_0(X)) = t_{0, 0} X_0 + t_{0, 1} X_1
\end{align}
\]
和
\[\begin{align}
\text{FWT}(\text{IFWT}_1(X)) = t_{1, 0} X_0 + t_{1, 1} X_1
\end{align}
\]
先只考虑 \((25)\),两边同时做逆 FWT,有
\[\begin{align}
\text{IFWT}_0(X) = \text{IFWT}(t_{0, 0} X_0 + t_{0, 1} X_1)
\end{align}
\]
即
\[\begin{align}
\text{IFWT}_0(X) = t_{0, 0} \cdot \text{IFWT}(X_0) + t_{0, 1} \cdot \text{IFWT}(X_1)
\end{align}
\]
同理,对于 \((26)\),有
\[\begin{align}
\text{IFWT}_1(X) = t_{1, 0} \cdot \text{IFWT}(X_0) + t_{1, 1} \cdot \text{IFWT}(X_1)
\end{align}
\]
这样,我们只用拼接 \(\text{IFWT}_0(X)\) 和 \(\text{IFWT}_1(X)\) 即可。
观察 FWT 的 \((14)\) 与 \((15)\) 和逆 FWT 的 \((28)\) 与 \((29)\),你会发现它们很像,只有系数上的区别,所以我们可以将 FWT 和逆 FWT 写在一个函数内。
下面,让我们来讨论几组常见的卷积。记 \(\vee\) 表示按位或运算,\(\wedge\) 表示按位与运算,\(\oplus\) 表示按位异或运算。
一些常见卷积
按位或
此时 \(\otimes = \text{or}\),那么我们有构造
\[\begin{align}
T = \begin{bmatrix}
1 & 0 \\
1 & 1
\end{bmatrix}
\end{align}
\]
显然,它满足 \((7)\),即
\[\begin{align}
c_{i, k} c_{i, l} = c_{i, k \vee l}
\end{align}
\]
那么有
\[\begin{align}
\text{FWT}_{0}(X) = \text{FWT}(X_0)
\end{align}
\]
和
\[\begin{align}
\text{FWT}_{1}(X) = \text{FWT}(X_0) + \text{FWT}(X_1)
\end{align}
\]
即
\[\begin{align}
\text{FWT}(X) = \text{merge}(\text{FWT}(X_0), \text{FWT}(X_0) + \text{FWT}(X_1))
\end{align}
\]
这里所有的定义和前面一样,其中 \(\text{merge}(U,V)\) 表示将数组 \(U,V\) 首尾拼接成一个新数组。
现在我们需要做逆 FWT 变换,有
\[\begin{align}
T^{-1} = \begin{bmatrix}
1 & 0 \\
-1 & 1
\end{bmatrix}
\end{align}
\]
那么有
\[\begin{align}
\text{IFWT}_0(X) = \text{IFWT}(X_0)
\end{align}
\]
和
\[\begin{align}
\text{IFWT}_1(X) = \text{IFWT}(X_1) - \text{IFWT}(X_0)
\end{align}
\]
即
\[\begin{align}
\text{IFWT}(X) = \text{merge}(\text{IFWT}(X_0), \text{IFWT}(X_1) - \text{IFWT}(X_0))
\end{align}
\]
按位与
有构造
\[\begin{align}
T = \begin{bmatrix}
1 & 1 \\
0 & 1
\end{bmatrix},
T^{-1} = \begin{bmatrix}
1 & -1 \\
0 & 1
\end{bmatrix}
\end{align}
\]
按位异或
有构造
\[\begin{align}
T = \begin{bmatrix}
1 & 1 \\
1 & -1
\end{bmatrix},
T^{-1} = \begin{bmatrix}
0.5 & 0.5 \\
0.5 & -0.5
\end{bmatrix}
\end{align}
\]
在这里,你需要使用乘法逆元来计算 \(0.5\)。
具体实现
参考代码:
void g_or(int *f, int tp) {
for (int x = 2; x <= n; x <<= 1) {
int k = x >> 1;
for (int i = 0; i < n; i += x) {
for (int j = 0; j < k; j++) {
f[i + j + k] = (f[i + j + k] + f[i + j] * tp + P) % P;
}
}
}
}
void g_and(int *f, int tp) {
for (int x = 2; x <= n; x <<= 1) {
int k = x >> 1;
for (int i = 0; i < n; i += x) {
for (int j = 0; j < k; j++) {
f[i + j] = (f[i + j] + f[i + j + k] * tp + P) % P;
}
}
}
}
void g_xor(int *f, int tp) {
for (int x = 2; x <= n; x <<= 1) {
int k = x >> 1;
for (int i = 0; i < n; i += x) {
for (int j = 0; j < k; j++) {
f[i + j] = (f[i + j] + f[i + j + k]) % P;
f[i + j + k] = (f[i + j] - (2 * f[i + j + k] % P) + P) % P;
f[i + j] = f[i + j] * tp % P;
f[i + j + k] = f[i + j + k] * tp % P;
}
}
}
}
未完待续...