卷积全家桶

你可能会问为什么这里只有 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;
            }
        }
    }
}

未完待续...

posted @ 2025-02-16 21:00  Eliauk_FP  阅读(66)  评论(2)    收藏  举报