本文介绍 Dirichlet 卷积和 Dirichlet 生成函数.

Dirichlet 卷积

数论函数 和 的 Dirichlet 卷积(Dirichlet convolution),记作 ,定义为数论函数

Dirichlet 卷积是数论函数的重要运算.数论函数的许多性质都是通过这个运算挖掘出来的.

例子

  1. 单位函数 是莫比乌斯函数 和常数函数 的 Dirichlet 卷积:
  1. 除数个数函数 是常数函数 和它自身的 Dirichlet 卷积:
  1. 除数和函数 是恒等函数 和常数函数 的 Dirichlet 卷积:
  1. 欧拉函数 是恒等函数 和莫比乌斯函数 的 Dirichlet 卷积:

莫比乌斯反演 就是利用 对数论函数恒等式进行变形.

性质

Dirichlet 卷积具有一系列代数性质.

定理

设 都是数论函数.那么,有:

  1. 交换律:.
  2. 结合律:.
  3. 分配律:.
  4. 单位元:,其中, 是卷积单位元, 是 Iverson 括号.
  5. 逆元:当且仅当 时,存在 使得 ,且 称为 的 Dirichlet 逆元(Dirichlet inverse),可以记作 .而且,逆元 满足递推公式

用抽象代数的语言说,这些代数性质说明,全体数论函数在(逐点)加法运算和 Dirichlet 卷积运算下构成 交换环,且它的全体可逆元就是那些在 处取非零值的函数.这个环称为 Dirichlet 环(Dirichlet ring).

积性函数是一类特殊的数论函数.它对于 Dirichlet 卷积和 Dirichlet 逆都是封闭的.

定理

设 是积性函数,那么, 也是积性函数.而且,逆元 一定存在,它也是积性函数.

用抽象代数的语言说,全体积性函数在 Dirichlet 卷积运算下构成 Dirichlet 环乘法群的 子群.

更为特殊的是完全积性函数.

定理

设 是完全积性函数, 是数论函数.那么,有:

  1. 分配律:.
  2. 逆元:,只要 存在.
  3. 积性函数 是完全积性函数,当且仅当 ,其中, 是 莫比乌斯函数.

用抽象代数的语言说,如果 是完全积性函数,映射 是 Dirichlet 环的 自同态.

Dirichlet 生成函数

与 Dirichlet 卷积紧密相关的是 Dirichlet 生成函数.

数论函数 ——也就是数列 ——对应的 Dirichlet 生成函数(Dirichlet series generating function,DGF)定义为形式 Dirichlet 级数(formal Dirichlet series):

级数中的 是形式变元.常见的 Dirichlet 生成函数中, 往往可以看作是复变量,进而讨论 Dirichlet 级数的解析性质,但这超出了算法竞赛的范围.

Dirichlet 生成函数的乘积对应着相应的数论函数的 Dirichlet 卷积:

定理

对于数论函数 及其 Dirichlet 生成函数 ,它们的 Dirichlet 卷积 的生成函数等于 .

利用 Dirichlet 卷积和 Dirichlet 生成函数乘积之间的对应关系,可以从 Dirichlet 生成函数的角度理解 Dirichlet 卷积的性质.由于形式 Dirichlet 级数的乘法运算满足交换律、结合律、对加法的分配律,数论函数的 Dirichlet 卷积运算满足同样的代数性质.

Euler 乘积

积性函数的特殊性同样反映在 Dirichlet 生成函数上.由于整数有 唯一分解定理,积性函数 的生成函数 可写成如下形式:

这意味着, 可以分解为若干 的乘积,且每个 对应的数论函数都只在 的幂次处可能取非零值.这一无穷乘积也称为 Euler 乘积(Euler product).如果 和 都能分解成类似的形式,那么它们的乘积同样如此;将这一观察对应到数论函数上,就是积性函数的 Dirichlet 卷积仍然是积性函数.

进一步地,如果 还是完全积性函数,那么 ,上式可以继续简化:

与积性函数不同,完全积性函数的 Dirichlet 生成函数的形式在乘法运算下并不具有封闭性,因此,完全积性函数的 Dirichlet 卷积和 Dirichlet 逆都未必是完全积性函数,但一定是积性函数.

例子

  1. 单位函数 是完全积性函数.它的 Dirichlet 生成函数是关于不定元 的常值函数

  2. 常数函数 是完全积性函数.它的 Dirichlet 生成函数是 Riemann 函数

  3. 莫比乌斯函数 是常数函数的 Dirichlet 逆.它的 Dirichlet 生成函数是 的倒数:

  4. 幂函数 是完全积性函数.特别地,当 时,它就是常数函数;当 时,它就是恒等函数.它的 Dirichlet 生成函数是

  5. 欧拉函数 是积性函数.它的 Dirichlet 生成函数是

    结合幂函数的 Dirichlet 函数表达式,就得到 .

  6. 约数函数 是积性函数.它的 Dirichlet 生成函数是

    结合幂函数的 Dirichlet 表达式,就得到 .这正是 的定义式.

  7. 无平方因子数的指示函数 是积性函数.它的 Dirichlet 生成函数是

应用

Dirichlet 生成函数可以用于将积性函数表示为 Dirichlet 卷积.

例如在杜教筛的过程中,要计算积性函数 的前缀和,需要找到另一个积性函数 使得 和 都可以快速求前缀和.可以利用 Dirichlet 生成函数推导这一过程.

以杜教筛一节的例题 Luogu P3768 简单的数学题 为例,需要对 构造满足上述条件的数论函数 .由于 是积性函数,它的 Dirichlet 生成函数为

对比幂函数的 Dirichlet 生成函数可知,只要取 ,就有 .两者都是可以快速计算前缀和的.

Dirichlet 卷积的计算

本节讨论 Dirichlet 卷积的计算问题,即给定序列 和 ,求解 Dirichlet 卷积 的前若干项 的问题.根据涉及到的函数性质,算法的复杂度也略有不同.

一般情形

如果 都没有特殊性质,那么 Dirichlet 卷积的计算只能利用其定义:

枚举 和 ,将贡献 累加到 上即可.枚举复杂度为

参考实现如下:

参考实现

// Compute the Dirichlet convolution h = f * g.
auto dirichlet_convolute(const std::vector<int>& f, const std::vector<int>& g) {
  int n = f.size() - 1;
  std::vector<int> h(n + 1);
  for (int k = 1; k <= n; ++k) {
    for (int d = 1; k * d <= n; ++d) {
      h[k * d] += f[k] * g[d];
    }
  }
  return h;
}

与积性函数卷积的情形

如果 是积性函数,那么可以利用 Euler 乘积加速 Dirichlet 卷积的计算.计算 相当于计算它的 Dirichlet 生成函数 中各项的系数.由于

其中, 是 的 Euler 乘积分解中的因式,它只包含 的幂次处的系数:

那么,从 开始,遍历所有不超过 的素数 ,将 逐一乘上去,同样可以得到最终结果 .将 乘上去时,直接应用一般情形中的暴力枚举算法即可.总枚举次数

最后一步复杂度的估计与 Eratosthenes 筛法 复杂度的证明一致.所以,本算法的时间复杂度为 .

参考实现如下:

参考实现

// Compute the Dirichlet convolution h = f * g.
// Assume that g is multiplicative.
auto dirichlet_convolute(const std::vector<int>& f, const std::vector<int>& g) {
  int n = f.size() - 1;
  std::vector<int> h(f);
  std::vector<bool> vis(n + 1);
  for (int i = 2; i <= n; ++i) {
    if (vis[i]) continue;
    // Reverse the order for in-place computation.
    for (int k = n / i * i; k; k -= i) {
      vis[k] = true;
      int d = k;
      while (true) {
        d /= i;
        h[k] += h[d] * g[k / d];
        if (d % i) break;
      }
    }
  }
  return h;
}

特别地,当积性函数 是完全积性函数或其 Dirichlet 逆时,例如当 或 时,那么算法可以进一步简化。此时,Dirichlet 卷积 的计算可以采用常数更小的 差分 算法,但是算法时间复杂度仍为 。

结果为积性函数的情形

最后,考虑 是积性函数的情形.特别地,当 都是积性函数时, 就是积性函数.要计算 ,只需要确定它在素数幂处的取值,就可以通过 线性筛 在 时间内计算.而对于素数幂 处的取值 直接暴力计算即可:

这些暴力计算需要的枚举次数为

因此,这一算法的总时间复杂度为 .

参考实现如下:

参考实现

// Compute the Dirichlet convolution h = f * g.
// Assume that h is multiplicative.
auto dirichlet_convolute(const std::vector<int>& f, const std::vector<int>& g) {
  int n = f.size() - 1;
  std::vector<int> h(n + 1), primes, rem(n + 1), lpf(n + 1);
  std::vector<bool> vis(n + 1);
  h[1] = 1;
  for (int x = 2; x <= n; ++x) {
    if (!vis[x]) {
      primes.push_back(x);
      rem[x] = 1;
      lpf[x] = x;
    }
    for (int p : primes) {
      if (x * p > n) break;
      vis[x * p] = true;
      rem[x * p] = x % p ? x : rem[x];
      lpf[x * p] = p;
      if (x % p == 0) break;
    }
    if (rem[x] == 1) {  // prime powers.
      for (int k = x; k; k /= lpf[x]) {
        h[x] += f[k] * g[x / k];
      }
    } else {  // other cases.
      h[x] = h[rem[x]] * h[x / rem[x]];
    }
  }
  return h;
}

参考资料与注释