跳转至

高维前缀和、子集 dp、快速沃尔什变换

高维前缀和

二维前缀和

已知二维数组 \(a\),求 \(a\) 的二维前缀和。

这题我会,容斥!

二维前缀和

\(a\) 的二维前缀和为 \(s\)

\[ \begin{aligned} s(i, j) = a(i, j) + s(i - 1, j) + s(i, j - 1) - s(i - 1, j - 1) \\ \end{aligned} \]

你说得对,但如果我掏出十维甚至九维前缀和,阁下该如何应对?

事实上,高维前缀和可以一维一维地求。

高维前缀和

\(a\)\(n\) 维,第 \(k\) 维的长度为 \(m_k\)\(s_k\)\(a\) 只对前 \(k\) 维求和的前缀和,即:

\[ \begin{aligned} s_k(i_1, \cdots, i_n) = \sum_{j_1 = 1}^{i_1} \sum_{j_2 = 1}^{i_2} \cdots \sum_{j_k = 1}^{i_k} s_k(j_1, \cdots, j_k, i_{k + 1}, \cdots, i_{n}) \\ \end{aligned} \]

显然 \(s_0\) 即为 \(a\)

「一维一维地求」即:

\[ \begin{aligned} s_k(i_1, \cdots, i_n) &= \sum_{j_1 = 1}^{i_1} \sum_{j_2 = 1}^{i_2} \cdots \sum_{j_k = 1}^{i_k} s_k(j_1, \cdots, j_k, i_{k + 1}, \cdots, i_{n}) \\ &= \sum_{j_k = 1}^{i_k} \left[ \sum_{j_1 = 1}^{i_1} \sum_{j_2 = 1}^{i_2} \cdots \sum_{j_{k - 1} = 1}^{i_{k - 1}} s_k(j_1, \cdots, j_k, i_{k + 1}, \cdots, i_{n}) \right] \\ &= \sum_{j_k = 1}^{i_k} s_k(i_1, \cdots, i_{k - 1}, j_k, i_{k + 1}, \cdots, i_n) \\ \end{aligned} \]

值得注意的是,维度的顺序并不重要。

子集 dp

子集 dp

已知全集 \(\mathbf U\) 中有 \(n\) 个元素,关于集合的函数 \(f(\mathbf S)\),求 \(g(\mathbf S) = \displaystyle\sum_{\mathbf T \subset \mathbf S} f(\mathbf S)\)

\(\mathbf S_k \in \{0, 1\}\) 表示集合 \(\mathbf S\) 中第 \(k\) 种元素是否存在。可以发现,\(\mathbf T \subset \mathbf S\)\(\forall i, \mathbf T_i \leq \mathbf S_i\) 是充要条件,因此 \(f\) 可以看作一个 \(n\) 维,每一维长度为 \(2\) 的数组,\(g\) 实际上就是 \(f\) 的前缀和,使用高维前缀和的方法即可解决。

第一种实现
1
2
3
4
for (int l = 1; l < (1 << n); l <<= 1)
    for (int i = 0; i < (1 << n); i += (l << 1))
        for (int j = i; j < i ^ l; ++j)
            a[j ^ l] += a[j]; // 这里 j ^ l = j | l = j + l,上面 i ^ l 同理

第一层循环枚举维度,第二层循环枚举还未计算的维度,第三层循环枚举已计算的维度。

第二种实现
1
2
3
4
for (int i = 1; i < (1 << n); i <<= 1)
    for (int j = 0; j < n; ++j)
        if (j & i)
            a[j] += a[j ^ i]; // 这里 j ^ i = j - i

第一层循环枚举维度,第二层循环枚举全集的所有子集,通过 if 保证每一维只被计算一次。

第二种实现方式会比第一种慢一些,但时间复杂度相同。

实际上,子集 dp 不只能处理求和的问题(也可以求最大值等),也不只能处理子集的问题(若模板中的求和条件改为 \(\mathbf S \subset \mathbf T\),求后缀和即可),也可以用差分将 \(g\) 转化为 \(f\)

快速沃尔什变换(FWT)

FWT

已知 \(a, b\) 两个下标从 \(0\) 开始,长度为 \(2^n\) 的序列,求序列 \(c\),其中:

\[ c_i = \sum_{j \oplus k = i} a_jb_k \]

其中 \(\oplus\)\(\operatorname{or}\)\(\operatorname{and}\)\(\operatorname{xor}\)

FWT 与 FFT 类似,都是将原序列经过 \(O(n \times 2^n)\) 的变换后相乘,再逆变换得到结果。设 \(\tilde a, \tilde b, \tilde c\) 分别为 \(a, b, c\) 经过 FWT 后的结果。

或卷积

或卷积中,\(\tilde a_i = \displaystyle\sum_{i \operatorname{or} j = i} a_j\),可以发现:

\[ \begin{aligned} \tilde a_i \tilde b_i &= \left(\sum_{i \operatorname{or} j = i} a_j \right) \left(\sum_{i \operatorname{or} k = i} b_k \right) \\ &= \sum_{i \operatorname{or} j = i}\sum_{i \operatorname{or} k = i} a_j b_k \\ \end{aligned} \]

众所周知,位运算可以和集合运算对应,将 \(i, j, k\) 对应到 \(\mathbf A, \mathbf B, \mathbf C\)\(i \operatorname{or} j = i, i \operatorname{or} k = i\) 可以转化为 \(\mathbf B \subset \mathbf A, \mathbf C \subset \mathbf A\),根据集合运算,这个条件的充要条件为 \((\mathbf B \cup \mathbf C) \subset A\),即 \(i \operatorname{or} (j \operatorname{or} k) = i\)。因此:

\[ \begin{aligned} \tilde a_i \tilde b_i &= \sum_{i \operatorname{or} j = i}\sum_{i \operatorname{or} k = i} a_j b_k \\ &= \sum_{i \operatorname{or} (j \operatorname{or} k) = i} a_j b_k \\ &= \sum_{i \operatorname{or} l = i} \sum_{j \operatorname{or} k = l} a_j b_k \\ &= \sum_{i \operatorname{or} l = i} c_l \\ &= \tilde c_i \\ \end{aligned} \]

可以发现 \(\tilde a_i\) 实际上就是上文子集 dp 的模板,使用高维前缀和即可求出。而逆变换只需要将前缀和变为差分即可。

第一种实现
1
2
3
4
for (int l = 1; l < n; l <<= 1)
    for (int i = 0; i < n; i += (l << 1))
        for (int j = i; j < (i ^ l); ++j)
            a[j ^ l] = inv ? a[j ^ l] - a[j] : a[j ^ l] + a[j];
第二种实现
1
2
3
4
for (int i = 1; i < n; i <<= 1)
    for (int j = 0; j < n; ++j)
        if (j & i)
            a[j] = inv ? a[j] - a[j ^ i] : a[j] + a[j ^ i];

与卷积

与卷积和或卷积类似,\(\tilde a_i = \displaystyle\sum_{i \operatorname{and} j = i} a_j\)。在求 \(\tilde a\) 时,只需要将高维前缀和改为后缀和。

第一种实现
1
2
3
4
for (int l = 1; l < n; l <<= 1)
    for (int i = 0; i < n; i += (l << 1))
        for (int j = i; j < (i ^ l); ++j)
            a[j] = inv ? a[j] - a[j ^ l] : a[j] + a[j ^ l];
第二种实现
1
2
3
4
for (int i = 1; i < n; i <<= 1)
    for (int j = 0; j < n; ++j)
        if (j & i)
            a[j ^ i] = inv ? a[j ^ i] - a[j] : a[j ^ i] + a[j];

异或卷积

异或卷积和高维前缀和关系不大。

定义 \(i \otimes j = \operatorname{popcount}(i \operatorname{and} j) \bmod 2\),其中 \(\operatorname{popcount}(x)\)\(x\) 二进制表示中 \(1\) 的个数。

\(\otimes\) 也有类似的性质:

\[ \begin{aligned} &\operatorname{popcount}(i \operatorname{and} (j \operatorname{xor} k)) \\ =& \operatorname{popcount}(i \operatorname{and} j) + \operatorname{popcount}(i \operatorname{and} k) - 2 \times \operatorname{popcount}(i \operatorname{and} (j \operatorname{and} k)) \\ \end{aligned} \]

减去一个偶数后奇偶性不变,因此:

\[ \begin{aligned} \operatorname{popcount}(i \operatorname{and} (j \operatorname{xor} k)) \equiv \operatorname{popcount}(i \operatorname{and} j) + \operatorname{popcount}(i \operatorname{and} k) \pmod 2 \\ \end{aligned} \]

因为模 \(2\) 意义下的加法和异或是相同的,因此有 \(i \otimes (j \operatorname{xor} k) = (i \otimes j) \operatorname{xor} (i \otimes k)\)

\(\tilde a_i = \displaystyle\sum_{i \otimes j = 0} a_j - \sum_{i \otimes j = 1} a_j\),有:

\[ \begin{aligned} \tilde a_i \tilde b_i &= \left(\sum_{i \otimes j = 0} a_j - \sum_{i \otimes j = 1} a_j \right) \left(\sum_{i \otimes k = 0} b_k - \sum_{i \otimes k = 1} b_k \right) \\ &= \left(\sum_{i \otimes j = 0} a_j \right)\left(\sum_{i \otimes k = 0} b_k \right) - \left(\sum_{i \otimes j = 1} a_j \right)\left(\sum_{i \otimes k = 0} b_k \right) - \left(\sum_{i \otimes j = 0} a_j \right)\left(\sum_{i \otimes k = 1} b_k \right) + \left(\sum_{i \otimes j = 1} a_j \right)\left(\sum_{i \otimes k = 1} b_k \right) \\ &= \sum_{i \otimes j = 0} \sum_{i \otimes k = 0} a_j b_k - \sum_{i \otimes j = 1} \sum_{i \otimes k = 0} a_j b_k - \sum_{i \otimes j = 0} \sum_{i \otimes k = 1} a_j b_k + \sum_{i \otimes j = 1} \sum_{i \otimes k = 1} a_j b_k \\ &= \sum_{i \oplus (j \operatorname{xor} k) = 0} a_j b_k - \sum_{i \oplus (j \operatorname{xor} k) = 1} a_j b_k \\ &= \sum_{i \oplus l = 0} \sum_{j \operatorname{xor} k = l} a_j b_k - \sum_{i \oplus l = 1} \sum_{j \operatorname{xor} k = l} a_j b_k \\ &= \sum_{i \oplus l = 0} c_l - \sum_{i \oplus l = 0} c_l \\ &= \tilde c_i \\ \end{aligned} \]

至于 \(\tilde a\) 的求法,设 \(i\) 的二进制第 \(k\) 位为 \(i_k\)\(f_k(i_1, \cdots, i_n)\) 为在后 \(n - k\) 位相同且不考虑、只考虑前 \(k\) 位的情况下的 \(\tilde a_i\) 值,\(f_0\)\(a\)。具体来说,对于 \(n = 2\)\(f_1(1, 1) = f_0(0, 1) - f_0(1, 1)\),虽然 \((11)_2 \operatorname{and} (01)_2 = (01)_2\),有一个 \(1\),但因为我们只考虑第一位,第二位往后相同且不考虑,所以将第二位往后的 \(1\) 忽略。考虑 \(f_k\) 的递推:

\[ \begin{aligned} f_k(i_1, \cdots i_{k - 1}, 0, i_{k + 1} \cdots i_n) &= f_{k - 1}(i_1, \cdots i_{k - 1}, 0, i_{k + 1} \cdots i_n) + f_{k - 1}(i_1, \cdots i_{k - 1}, 1, i_{k + 1} \cdots i_n) \\ f_k(i_1, \cdots i_{k - 1}, 1, i_{k + 1} \cdots i_n) &= f_{k - 1}(i_1, \cdots i_{k - 1}, 0, i_{k + 1} \cdots i_n) - f_{k - 1}(i_1, \cdots i_{k - 1}, 1, i_{k + 1} \cdots i_n) \\ \end{aligned} \]

因为 \(0 \operatorname{and} 0 = 0 \operatorname{and} 1 = 0\),所以若第 \(k\) 位为 \(0\),加入第 \(k\) 位之后第 \(k\) 位对与运算后结果中 \(1\) 个数没有影响。而 \(1 \operatorname{and} 1 = 1\),所以若第 \(k\) 位为 \(1\),加入第 \(k\) 位之后仅当第 \(k\) 位为 \(1\) 时才对 \(1\) 的个数的奇偶性有影响。

逆变换即为:

\[ \begin{aligned} f_k(i_1, \cdots i_{k - 1}, 0, i_{k + 1} \cdots i_n) &= \frac{f_{k - 1}(i_1, \cdots i_{k - 1}, 0, i_{k + 1} \cdots i_n) + f_{k - 1}(i_1, \cdots i_{k - 1}, 1, i_{k + 1} \cdots i_n)}{2} \\ f_k(i_1, \cdots i_{k - 1}, 1, i_{k + 1} \cdots i_n) &= \frac{f_{k - 1}(i_1, \cdots i_{k - 1}, 0, i_{k + 1} \cdots i_n) - f_{k - 1}(i_1, \cdots i_{k - 1}, 1, i_{k + 1} \cdots i_n)}{2} \\ \end{aligned} \]
第一种实现
1
2
3
4
5
6
7
8
for (int l = 1; l < n; l <<= 1)
    for (int i = 0; i < n; i += (l << 1))
        for (int j = i; j < (i ^ l); ++j) {
            int tmp1 = a[j], tmp2 = a[j ^ l];
            a[j] = tmp1 + tmp2, a[j ^ l] = tmp1 - tmp2;
            if (inv)
                a[j] >>= 1, a[j ^ l] >>= 1;
        }
第二种实现
1
2
3
4
5
6
7
8
for (int i = 1; i < n; i <<= 1)
    for (int j = 0; j < n; ++j)
        if (j & i) {
            int tmp1 = a[j ^ i], tmp2 = a[j];
            a[j ^ i] = tmp1 + tmp2, a[j] = tmp1 - tmp2;
            if (inv)
                a[j ^ i] >>= 1, a[j] >>= 1;
        }