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}^nlfloor n/i floor=mathcal{O}(nlog 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_{pleq n,pinmathbb{P}}lfloor n/p floor=mathcal{O}(nloglog 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}_ihat{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}(nlog n)) 地完成。

原文地址:https://www.cnblogs.com/whx1003/p/13978772.html