本文介绍 Dirichlet 卷积和 Dirichlet 生成函数.
Dirichlet 卷积
数论函数 和 的 Dirichlet 卷积(Dirichlet convolution),记作 ,定义为数论函数
Dirichlet 卷积是数论函数的重要运算.数论函数的许多性质都是通过这个运算挖掘出来的.
例子
- 单位函数 是莫比乌斯函数 和常数函数 的 Dirichlet 卷积:
- 除数个数函数 是常数函数 和它自身的 Dirichlet 卷积:
- 除数和函数 是恒等函数 和常数函数 的 Dirichlet 卷积:
- 欧拉函数 是恒等函数 和莫比乌斯函数 的 Dirichlet 卷积:
莫比乌斯反演 就是利用 对数论函数恒等式进行变形.
性质
Dirichlet 卷积具有一系列代数性质.
定理
设 都是数论函数.那么,有:
- 交换律:.
- 结合律:.
- 分配律:.
- 单位元:,其中, 是卷积单位元, 是 Iverson 括号.
- 逆元:当且仅当 时,存在 使得 ,且 称为 的 Dirichlet 逆元(Dirichlet inverse),可以记作 .而且,逆元 满足递推公式
证明
为验证交换律,直接计算可知
为验证结合律,直接计算可知
为验证分配律,直接计算可知
为验证 是单位元,直接计算可知
第二个等号是因为 仅在 即 时取得非零值.
最后,需要证明 存在,当且仅当 .对于任意 ,假设存在 使得 .这意味着
这实际上给出了一系列关于 取值的方程组,从中可以直接求出 .特别地,当 时,等式变为 ,所以 存在,至少要求 .而只要 ,可以直接解出
它可以用于递归计算 的取值.因此,逆元 存在,当且仅当 .
用抽象代数的语言说,这些代数性质说明,全体数论函数在(逐点)加法运算和 Dirichlet 卷积运算下构成 交换环,且它的全体可逆元就是那些在 处取非零值的函数.这个环称为 Dirichlet 环(Dirichlet ring).
积性函数是一类特殊的数论函数.它对于 Dirichlet 卷积和 Dirichlet 逆都是封闭的.
定理
设 是积性函数,那么, 也是积性函数.而且,逆元 一定存在,它也是积性函数.
证明
对于第一点,设 ,直接验证可知,对于 ,都有
其中,第三个等号改变求和顺序的逻辑是:当 遍历 的因数时, 的素因子可以根据它是 还是 的素因子分为两类,将两类中的素因子(计重复)分别乘起来得到 和 ,它们将分别遍历 和 的因数;反过来,根据 和 的因数 和 ,总是可以得到 的因数 .
对于第二点,设 ,考虑应用数学归纳法.首先,.此时,逆元的递归公式可以写作
所以,对于 且 ,有
其中,第二个等号用到了归纳假设,即对于 且 ,条件 成立.
用抽象代数的语言说,全体积性函数在 Dirichlet 卷积运算下构成 Dirichlet 环乘法群的 子群.
更为特殊的是完全积性函数.
定理
设 是完全积性函数, 是数论函数.那么,有:
- 分配律:.
- 逆元:,只要 存在.
- 积性函数 是完全积性函数,当且仅当 ,其中, 是 莫比乌斯函数.
证明
对于第一条,直接验证可知
其中,第三个等号用到了完全积性函数的性质: 对所有 都成立.
对于第二条,利用第一条就有
其中,最后一个等号只利用了 .由逆元定义,.
对于第三条,利用第二条和 可知,如果 是完全积性函数,那么
其中, 是常数函数.反过来,如果 是积性函数且 ,那么只需要证明对于所有素数 和 ,都有 成立,就能证明 是完全积性函数.为此,对 应用数学归纳法.归纳起点 处命题显然成立.对于任意 ,应用逆元递推公式,都有
其中,最后一个等号用到了归纳假设 .应用 ,就得到
代入前式,就得到
所以,归纳步骤成立.原命题得证.
用抽象代数的语言说,如果 是完全积性函数,映射 是 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 逆都未必是完全积性函数,但一定是积性函数.
例子
单位函数 是完全积性函数.它的 Dirichlet 生成函数是关于不定元 的常值函数
常数函数 是完全积性函数.它的 Dirichlet 生成函数是 Riemann 函数
莫比乌斯函数 是常数函数的 Dirichlet 逆.它的 Dirichlet 生成函数是 的倒数:
幂函数 是完全积性函数.特别地,当 时,它就是常数函数;当 时,它就是恒等函数.它的 Dirichlet 生成函数是
欧拉函数 是积性函数.它的 Dirichlet 生成函数是
结合幂函数的 Dirichlet 函数表达式,就得到 .
约数函数 是积性函数.它的 Dirichlet 生成函数是
结合幂函数的 Dirichlet 表达式,就得到 .这正是 的定义式.
无平方因子数的指示函数 是积性函数.它的 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; }