狄利克雷卷积
极难4狄利克雷卷积:数论函数的“乘法”
把函数“卷”起来,就像分糖果一样
我们熟悉加法和乘法,但你知道吗?函数之间也能进行运算。狄利克雷卷积就是一种特殊的“乘法”,它把两个数论函数 和 组合成一个新函数 。定义如下:
这个公式的意思是:对于每个正整数 ,我们枚举它的所有正因子 ,把 和 相乘,然后把所有乘积加起来。
你可以这样想象:老师给全班同学分糖果,糖果总数是 颗。老师先让每个小组长(对应因子 )按某种规则领走 颗糖,然后剩下的糖果()再按另一种规则分给组员 。最后把所有小组的得分加起来,就是全班的总分。这就是“卷”在一起——一个函数从因子的角度贡献,另一个函数从商的角度贡献。
为什么要学这个?——让复杂求和变简单
狄利克雷卷积是数论中的“乘法”。很多重要的数论恒等式都可以用卷积简洁地表达。比如:
- 恒等函数 与任何函数 的卷积:,这正是因子求和!
- 若 是莫比乌斯函数,则 ,其中 (单位函数)。
- 若 是欧拉函数,则 ,其中 。
这些关系让我们能轻松地在不同函数之间转换,就像用乘法分配律化简代数式一样。
卷积的性质——乘法的“好习惯”
狄利克雷卷积满足:
- 交换律:(因为因子对 对称,就像交换两个乘数的位置结果不变)。
- 结合律:(先卷哪两个都行)。
- 单位元:,其中 (就像乘法中的1,任何函数卷上它都不变)。
- 积性函数的卷积仍是积性函数:如果 和 都是积性函数,那么 也是积性函数。这个性质超级有用,后面会用到。
生活中的比喻:排队领零食
假设你在学校小卖部排队。队伍的长度 是总人数。每个人()手上有一种零食数量 ,他后面跟的人()有另一种零食数量 。那么整个队伍的总零食量就是 。如果队伍只有1个人(),那么只有 一种分法,结果就是 。如果队伍有6个人,可以分成1人和6人、2人和3人、3人和2人、6人和1人等多种方式,每种方式的两段零食数相乘再求和。
新手容易犯的错误
- 忘记 的情况:当 时,只有因子 ,求和只有一项 。很多同学会误以为求和为空,或者漏掉。
- 数组下标从0还是1开始:数论函数通常从 开始有意义。编程实现时,数组下标0可以空着,或者把0也当作索引,但要注意 一般不用。常见错误是循环从0开始,导致访问了无效数据。
- 循环边界写错:枚举因子对时,双重循环的终止条件要小心。例如
for d in range(1, n+1): for k in range(1, n//d + 1):确保 。如果写成k <= n就会越界或重复计算。 - 数据类型溢出:卷积结果可能很大,比如 的 时结果已经超过 int 范围。建议用
long long或 Python 的 int。
例子:计算一些简单的卷积
例1:设 ,,则 (除数之和函数)。所以 。
例2:设 ,,则 。我们已经知道这个和等于 ,即 。所以 。
例3:设 ,,则 。可以证明这个和等于 (因为欧拉函数的性质)。所以 。
这些例子展示了卷积如何把看似不同的函数联系起来。
编程实现:亲手算一算
给定两个函数 和 ,我们可以用数组表示它们(下标从1开始)。对于每个 ,枚举所有因子 即可。复杂度是 (因为因子求和的总次数是 )。
C++ 实现
#include <iostream>
#include <vector>
using namespace std;
// 计算 f * g 的前 n 项,结果存入 h[1..n]
// f, g 是长度为 n+1 的数组(下标从0开始,但只使用1..n)
void dirichlet_convolution(const vector<long long>& f, const vector<long long>& g, vector<long long>& h, int n) {
h.assign(n + 1, 0); // 初始化结果数组全为0
// 枚举所有可能的 d 和 k 使得 d*k <= n
for (int d = 1; d <= n; d++) { // d 是第一个函数的自变量
for (int k = 1; d * k <= n; k++) { // k 是第二个函数的自变量
// 对于每个因子对 (d, k),贡献到乘积 d*k 的位置
h[d * k] += f[d] * g[k];
}
}
}
int main() {
const int N = 10; // 计算前10项
vector<long long> f(N + 1), g(N + 1), h; // 定义三个数组,f和g长度为N+1
for (int i = 1; i <= N; i++) {
f[i] = i; // id 函数:f(n)=n
g[i] = 1; // 常函数1:g(n)=1
}
dirichlet_convolution(f, g, h, N);
cout << "id * 1 的前10项:" << endl;
for (int i = 1; i <= N; i++) {
cout << "h(" << i << ") = " << h[i] << endl;
}
// 结果应该等于除数之和函数 σ(n) = 所有因子的和
return 0;
}
Python 实现
def dirichlet_convolution(f, g, n):
"""
计算 f * g 的前 n 项,返回列表 h[1..n]
f, g 是索引从1开始的列表(长度至少 n+1)
"""
h = [0] * (n + 1) # 初始化结果列表,索引0占位
for d in range(1, n + 1): # d 从1到n
for k in range(1, n // d + 1): # k 从1到n//d,保证 d*k <= n
h[d * k] += f[d] * g[k] # 累加贡献
return h
# 示例
N = 10
f = [0] + [i for i in range(1, N + 1)] # id 函数:f[n]=n
g = [0] + [1] * N # 常函数1:g[n]=1
h = dirichlet_convolution(f, g, N)
print("id * 1 的前10项:")
for i in range(1, N + 1):
print(f"h({i}) = {h[i]}")
注意:上面代码中,双重循环的顺序是枚举 d 然后枚举 k。这等价于枚举所有因子对,计算复杂度为 (调和级数)。
卷积与积性函数
如果 和 都是积性函数(即对于互质的 有 且 ),那么它们的卷积 也是积性函数。这一性质可以用来证明许多恒等式。例如,因为 和 都是积性的,所以它们的卷积 也是积性的(实际上 是完全积性的)。
利用卷积,莫比乌斯反演可以写成:若 ,则 。这将在下一个知识点详细讲解。
小结
狄利克雷卷积是数论中的核心操作,它将因子求和统一为一种乘法。掌握卷积的语言,你就能把复杂的求和公式变得简洁优美。今后见到 ,你就能认出这是一个卷积。
思考:试用卷积证明 。(提示:先证明对质数幂成立,再利用积性。)
相关指引
- 想要更深入地理解卷积如何简化数论问题?请学习 莫比乌斯反演。
- 想知道哪些函数是积性的?请查看 积性函数 的知识点。
- 如果对因子求和还有疑问,回顾 因数与倍数 的基础知识。
例题精讲
在狄利克雷卷积中,以下哪个函数作为单位元(即对于任意数论函数 f,有 f * ε = f)?
关于狄利克雷卷积的运算性质,下列说法正确的是:
如果 f 和 g 都是积性函数,那么它们的狄利克雷卷积 f * g 也是积性函数。
以下伪代码实现狄利克雷卷积 (f * g)(n),请补全空白处,使得循环能够枚举 n 的所有正因子。
function convolution(f, g, n):
result = 0
for d in ___:
result += f[d] * g[n // d]
return result狄利克雷卷积满足结合律,即对于任意三个数论函数 f, g, h,有 (f * g) * h = f * (g * h)。