Dirichlet 前缀和与快速莫比乌斯变换(FMT)

一类常见的问题是:

给出一个 \(k^n\) 的数组,求出它的高维前缀和。

这种问题的最常见做法是 \(\mathcal{O}(k^n2^n)\) 的容斥做法,这种做法复杂度随着维度升高快速增加。

高维前缀和的思想就是对于每一个维度分别求和,这显然是正确的。具体来说,我们先考虑将一个 \(n\) 维数组改写成一维,那么一种显然的想法就是,如果原来的数组位置是 \((\alpha_0,\alpha_1,\cdots,\alpha_{n-1})\),那么可以压缩到 \(\alpha_0k^{0}+\alpha_1k^{1}+\cdots=\sum_{i=0}^{n-1}\alpha_ik^i\)。那么这时候我们发现,其实可以重复利用信息来化简计算:

for(int i = 0, cur = 1; i < n; ++i, cur = cur * k)
    for(int j = 0; j < pow(k, n); ++j)
        if((j / cur) % k < k - 1) a[j + cur] += a[j];

这为什么是对的呢?可以想象这是一张有向无环图,那么我们相当于是按照拓扑序在进行 dp,这显然是正确的。

这种方法不仅在这里适用,我们考虑一种常见的数论问题:

给出一个数列 \(a_n\),输出一个数列 \(b_n\),满足 \(b_n=\sum_{d|n}a_d\)

一种常见的做法是对于每一个 \(a_i\),枚举 \(i\) 的倍数,一个一个加上去,这样的复杂度是 \(\sum_{i=1}^n\lfloor n/i\rfloor=\mathcal{O}(n\log n)\) 的。

一种优化是,考虑唯一分解定理,每一个数都可以被表为素数空间上的一串数,或者说,所有的素数构成了一组正交基。于是我们可以使用类似的算法进行优化:

for(int i = 1; i <= cnt; ++i)
    for(int j = 1; j * prime[i] <= n; ++j)
        a[j * prime[i]] += a[j];

这就是 Dirichlet 前缀和。这样的复杂度是 \(\sum_{p\leq n,p\in\mathbb{P}}\lfloor n/p\rfloor=\mathcal{O}(n\log\log n)\) 的,和 Eratosthenes 筛法相同。

还有一类问题则是子集的问题,这就是快速莫比乌斯变换(FMT)。这种问题形式如下:

给出数列 \(a_n,b_n\),求出 \(c_n\) 满足 \(c_n=\sum_{i|j=n}a_ib_j\)

考虑 FFT 的过程,我们需要求出一个函数 \(\hat{a}=F(a)\) 可以将一个数列映射到另一个数列,使得 \(\hat{a}_i=\hat{b}_i\hat{c}_i\)

一种构造就是

\[\hat{a}_n=\sum_{i|n=n}a_i \]

也即是 \(k=2\) 情况下的 \(a\) 的高维前缀和。关于如何构造这类函数,请参考下篇博客

那么考虑逆变换 \(a=F^{-1}(\hat a)\),这可以看作是扣除贡献,于是:

void FMT(int *a, int N, int op) {
    for(int i = 1; i < N; i <<= 1)
      	for(int j = 0; j < N; ++j)
            if(~j & i) a[j + i] += op * a[j];
}

于是这种问题现在就可以 \(\mathcal{O}(n\log n)\) 地完成。

posted @ 2020-11-15 21:56  whx1003  阅读(256)  评论(0编辑  收藏  举报