本文介绍 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;
}

参考资料与注释