狄利克雷卷积与杜教筛
填坑说是。
0. 狄利克雷卷积#
在莫比乌斯反演那提过一点,现在来系统性的讲一下。
数论函数#
指定义域为 N+ 的函数。
我们定义两个数论函数 f,g 的加法为 (f+g)(n)=f(n)+g(n),数乘为 (xf)(n)=x⋅f(n),点乘为 (f⋅g)(n)=f(n)g(n)
以下所有内容中,数乘不会打符号,点乘会打点号,注意区分。
狄利克雷卷积#
我们定义两个数论函数 f,g 的狄利克雷卷积 f∗g 为 (f∗g)(n)=d∣n∑f(d)g(dn)=xy=n∑f(x)g(y)
对于一个运算,我们肯定要探寻它的性质。
- 狄利克雷卷积有交换律,即 f∗g=g∗f
- 有结合律,即 f∗(g∗h)=(f∗g)∗h
- 对数论函数加法与数乘有分配律,即 f∗(g+h)=f∗g+f∗h,(xf)∗g=x(f∗g)
- 两积性函数的狄利克雷卷积也是积性函数
证明
交换律#
(f∗g)(n)=xy=n∑f(x)g(y)x→y,y→xyx=n∑f(y)g(x)=(g∗f)(n)结合律#
((f∗g)∗h)(n)=xy=n∑uv=x∑f(u)g(v)h(y)x→uvuvy=n∑f(u)g(v)h(y)u→x,v→u,y→vxuv=n∑f(x)g(u)h(v)y=uvxy=n∑f(x)uv=y∑g(u)h(v)=xy=n∑f(x)(g∗h)(y)=(f∗(g∗h))(n)分配律#
(f∗(g+h))(n)=xy=n∑f(x)(g+h)(y)=xy=n∑f(x)(g(y)+h(y))=(xy=n∑f(x)g(y))+(xy=n∑f(x)h(y))=(f∗g)(n)+(f∗h)(n)证明了对加法分配律后对数乘分配律显然,(xf)∗g=x(f∗g)。
两积性函数的狄利克雷卷积也是积性函数#
设 f,g 为两积性函数,a,b∈N+,gcd(a,b)=1
显然有 (f∗g)(1)=f(1)g(1)=1。
(f∗g)(a)⋅(f∗g)(b)=d1∣a∑f(d1)g(d1a)⋅d2∣b∑f(d2)g(d2b)=d1∣a,d2∣b∑f(d1d2)g(d1d2ab)由于 gcd(a,b)=1,因此 ab 的所有因数都可以被唯一的表示为 a 的某一因数与 b 的某一因数之积,因此有 (f∗g)(a)⋅(f∗g)(b)=d∣ab∑f(d)g(dab)=(f∗g)(ab)。
狄利克雷卷积存在单位元,这个单位元是 ϵ(n)=[n=1]。可以自证一下这个为什么是单位元。
既然存在单位元那也就存在逆元,最重要的是莫比乌斯函数 μ(n) 是常数函数 1(n) 的逆元。
下面是一些狄利克雷卷积中常用的函数。
- 1(n)=1
- Idk(n)=nk,在 k=1 时可以记作 Id(n)
- φ(n),欧拉函数
- μ(n),莫比乌斯函数
- ϵ(n)=[n=1]
- σk(n)=d∣n∑dk,在 k=1 时可以记作 σ(n),在 k=0 时可以记作 d(n)
我们给出几个重要结论:
φ=Id∗μ,因为 n=d∣n∑φ(d),可以看作 Id=φ∗1,两边同时卷个 μ 得 φ=Id∗μ
σk(n)=Idk(n)∗1
好的狄利克雷卷积就差不多到这了。
1. 杜教筛#
好的正片开始,杜教筛的作用是在亚线性复杂度内计算出一个数论函数 f 的前缀和 i=1∑nf(i) 的。
以下内容中,我们记 Sf(n)=i=1∑nf(i),有时我会略去角标。杜教筛的主要思想,就是找到 S(n) 关于 S(⌊in⌋) 的递推式。
我们设有另一个数论函数 g,则有如下关系:
i=1∑n(f∗g)(i)=i=1∑nd∣i∑g(d)f(di)=d=1∑ng(d)i=1∑nf(di)[d∣i]=d=1∑ng(d)i=1∑⌊di⌋f(i)=i=1∑ng(i)S(⌊in⌋)我们进行移项,就得到了 g(1)S(n)=i=1∑n(f∗g)(n)−i=2∑ng(i)S(⌊in⌋)。
现在就是 g 的选取问题了,我们选择的 g 需要有以下几个特征:
- ∑i=1n(f∗g)(n) 能够快速计算。
- ∑i=1ng(n) 能够快速计算。
选出了满足 g 之后,我们就可以用数论分块算出 S(n) 了,其中的 S(⌊in⌋) 是可以递归下去计算的。
那这个 g 到底怎么选呢?一般采用注意力集中法。但是好像可以从 DGF 来考虑,我不会就是了。
时间复杂度我不会证明,直接给出结论:如果预处理 n32 范围内的前缀和,则时间复杂度是 O(n32) 的,否则是 O(n43) 的。
在实现时,我们需要对大于 n32 的值进行记忆化,不嫌麻烦可以开一个大小为 2n 的数组,对于 S(x),如果 x≤n 则存到对应位置,否则存到 n+⌊xn⌋,但是一般情况下没人卡这里的常数,所以可以直接上 unordered_map
。
下面给出一份基于 unordered_map
的实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
inline void Seele(int n = P) // P 是阈值
{
... // 线性筛出小一点的前缀和
for(int i = 1;i <= n;i++)
f[i] += f[i - 1]; // 计算前缀和
}
unordered_map<int,int> rec;
inline void getFSum(int n)
{
if(n <= P) // 已经被筛出来了
return f[n];
if(rec.count(n)) // 算过了
return rec[n];
int res = ...; // (f * g)(n) 的前缀和
for(int l = 2,r;l <= n;l = r + 1)
{
r = n / (n / l);
res -= (g(r) - g(l - 1)) * getFSum(n / l); // 第二个和式
}
return rec[n] = res; // 记忆化
}
好,我们现在讲几个例子。
我们先对 μ 构造,注意到 μ∗1=ϵ,所以 f=μ,g=1,递推式为 Sμ(n)=1−i=2∑nSμ(⌊in⌋)。
再来看 φ,注意到 φ∗1=Id,所以递推式为 Sφ(n)=2n(n+1)−i=2∑nSφ(⌊in⌋)。
推式子先。
i=1∑nj=1∑nijgcd(i,j)=d=1∑nd3i=1∑⌊dn⌋j=1∑⌊dn⌋ij[gcd(i,j)=1]=d=1∑nd3x=1∑⌊dn⌋μ(x)x2i=1∑⌊dxn⌋ij=1∑⌊dxn⌋j=T=1∑n(d∣T∑d3μ(dT)(dT)2)(i=1∑⌊Tn⌋i)2=T=1∑n(i=1∑⌊Tn⌋i)2T2(d∣T∑dμ(dT))=T=1∑n(i=1∑⌊Tn⌋i)2(T2φ(T))我们现在要筛 Id2⋅φ 的前缀和,注意到当 g=Id2 时,(f∗g)(n)=d∣n∑d2φ(d)(dn)2=n2d∣n∑φ(d)=n3,其前缀和是可以 O(1) 计算的,并且 g 的前缀和也是可以 O(1) 计算的。
2. 总结#
感觉杜教筛的核心就是推成前缀和与瞪眼找 g。