定义两个数论函数 f,g 的狄利克雷卷积 f∗g 为:
(f∗g)(n)=d∣n∑f(d)g(dn)
狄利克雷卷积有以下(容易证明的)基本性质:
- 交换律 f∗g=g∗f。
- 结合律 f∗(g∗h)=(f∗g)∗h。
- 分配律 f∗(g+h)=f∗g+f∗h。
几个常见的数论函数可以用狄利克雷卷积连接:μ∗1=ϵ,φ∗1=id,μ∗id=φ。
狄利克雷卷积的单位元为指示函数 ϵ(n)=[n=1]。
狄利克雷生成函数是研究积性函数的好工具。
对于数论函数 f(n),定义其的狄利克雷生成函数为:
F(z)=i≥1∑f(i)i−z
根据狄利克雷生成函数的定义,可以写出 Wolfram 代码:
DirichletGF[f_, z_] := Sum[f[n] n^(-z), {n, 1, Infinity}]
为了方便,下面我们用加粗的大写字母来表示一个函数的狄利克雷生成函数。
狄利克雷生成函数有一个重要性质:
定理:对于积性函数 f(n) 及其狄利克雷生成函数 F(z),有:
F(z)=p∈P∏i≥0∑f(pi)p−iz
证明是容易的,根据算术基本定理以及积性函数的定义,取每个数的质因数分解即可得到。
狄利克雷生成函数的卷积,对应数论函数的狄利克雷卷积:
F(z)⋅G(z)=i≥1∑j≥1∑f(i)g(j)(ij)−z=d≥1∑d−zi∣d∑f(i)g(id)=d≥1∑(f∗g)(z)⋅d−z
指示函数 ϵ(n)=[n=1]。它的狄利克雷生成函数为:
E(z)=i≥1∑ϵ(i)i−z=1−z=1
常数函数 1(n)=1(为了防止歧义,下面用 c(n) 代指常数函数)。它的狄利克雷生成函数为:
C(z)=i≥1∑i−z=ζ(z)
其中 ζ(z) 就是著名的黎曼 zeta 函数。
幂函数 idk(z)=zk,它的狄利克雷生成函数为:
IDk(z)=i≥1∑ik⋅i−z=i≥1∑i−(z−k)=ζ(z−k)
幂函数的重要特例是恒等函数 id(z)=z,它的狄利克雷生成函数为 ζ(z−1)。
根据基本性质 μ∗1=ϵ,易得莫比乌斯函数 μ(n) 的狄利克雷生成函数为:
M(z)=C(z)E(z)=ζ(z)1
根据基本性质 μ∗id=φ,易得欧拉函数 φ(n) 的狄利克雷生成函数为:
Φ(z)=M(z)⋅ID(z)=ζ(z)ζ(z−1)
根据除数函数的定义 idk∗1=σk,易得除数函数 σk(n) 的狄利克雷生成函数为:
Σk(z)=IDk(z)⋅C(z)=ζ(z)ζ(z−k)
除数函数的特例是约数个数函数 d(n)=σ1(n),它的狄利克雷生成函数为 ζ(z)ζ(z−1)。
整理:
函数 | 中文名 | 狄利克雷生成函数 |
---|
ϵ | 指示函数 | E(z)=1 |
1 | 常数函数 | C(z)=ζ(z) |
idk | 幂函数 | IDk(z)=ζ(z−k) |
id | 恒等函数 | ID(z)=ζ(z−1) |
μ | 莫比乌斯函数 | M(z)=ζ(z)1 |
φ | 欧拉函数 | Φ(z)=ζ(z)ζ(z−1) |
σk | 除数函数 | Σk(z)=ζ(z)ζ(z−k) |
d | 约数个数函数 | D(z)=ζ(z)ζ(z−1) |
我们可以用 Wolfram 语言来检验这些结果:
DirichletGF[f_, z_] := Sum[f[n] n^(-z), {n, 1, Infinity}]
DirichletGF[If[# == 1, 1, 0] &, z] (* 指示函数,期望结果 1 *)
DirichletGF[1 &, z] (* 常数函数,期望结果 Zeta[z] *)
DirichletGF[#^k &, z] (* 幂函数,期望结果 Zeta[-k+z] *)
DirichletGF[MoebiusMu, z] (* 莫比乌斯函数,期望结果 1/Zeta[z] *)
DirichletGF[EulerPhi, z] (* 欧拉函数,期望结果 Zeta[-1+z]/Zeta[z] *)
DirichletGF[DivisorSigma[k, #] &, z] (* 除数函数,期望结果 Zeta[z] Zeta[-k+z] *)
求数论函数前缀和是一个经典的问题,用线性筛法可以做到 O(n),不过这并不是终点,高级筛法一般用于在亚线性时间复杂度内求数论函数的前缀和。
常见的高级筛法有杜教筛、Min_25 筛、洲阁筛、Powerful Number 筛等等。
杜教筛是最基本的高级筛法,它的时间复杂度较为优秀(O(n2/3)),但并不是所有数论函数都可以用杜教筛求和。
假设我们要求 f(n) 的前缀和 F(n),则需要构造两个函数 g,h 使得 f∗g=h。
记 G,H 分别为 g,h 的前缀和,那么可以推式子:
H(n)=i=1∑nh(n)=i=1∑nd∣i∑f(i)g(di)=i=1∑ng(i)d≤n,d∣i∑f(di)=i=1∑ng(i)i=1∑⌊in⌋f(d)=i=1∑ng(i)F(⌊in⌋)=g(1)F(n)+i=2∑ng(i)F(⌊in⌋)
故:
F(n)=g(1)1(H(n)−i=2∑ng(i)F(⌊in⌋))
如果我们能够快速求出 G(n) 以及 H(n),那么我们就可以通过整除分块求后面的和式(注意和式中同样涉及到 F,可以向下递归并记忆化)的方法来求出 F(n)。
可以证明这一算法的时间复杂度是 O(n2/3)(假设求 H(n) 的时间复杂度为 O(1),并使用某些方法如线性筛,预处理前 O(n2/3) 的函数值)。
51Nod 1224 莫比乌斯函数之和:求 μ 的前缀和。
由于 μ∗1=ϵ,而 1,ϵ 的前缀和都是容易的,因此可以用杜教筛来做。时间复杂度 O(n2/3)。完整代码。
为了节省篇幅,这里只给出实现关键代码,下同:
i64 get_mu(i64 n){
if(n <= BOUND) return mu[n];
if(mp.count(n)) return mp[n];
i64 ans = 1;
for(i64 l=2,r;l<=n;l=r+1){
r = n / (n / l);
ans -= (r - l + 1) * get_mu(n / l);
}
return mp[n] = ans;
}
51Nod 1239 欧拉函数之和:求 φ 的前缀和。
由于 φ∗1=id,而 1,id 的前缀和都是容易的,因此也可以用杜教筛来做。时间复杂度 O(n2/3)。完整代码。
int get_phi(i64 n){
if(n <= BOUND) return phi[n];
if(mp.count(n)) return mp[n];
int res = (((__int128)n * (n + 1)) >> 1) % mod;
for(i64 l=2,r;l<=n;l=r+1){
r = n / (n / l);
res = Sub(res, Mul((r - l + 1) % mod, get_phi(n / l)));
}
return mp[n] = res;
}
HDU 5608 function:给定定义域为 N 的函数 f,满足 n2−3n+2=∑d∣nf(d),求 f 的前缀和。
若令 g(n)=n2−3n+2,则题目给出的等式等价于 g=f∗1,这是一个可以用杜教筛来处理的形式。
由于 1 的前缀和是容易的,并且 g 的前缀和:
i=1∑ni2−3i+2=31(n3−3n2+2n)
也是容易求出的。
唯一的难点在于如何求前 O(n2/3) 的 f 的前缀和。注意到 g=f∗1 可以改写为 f=g∗1−1,而 1 的狄利克雷生成函数为 ζ(z),它的逆 ζ(z)1 是 μ 的生成函数。故 f=g∗μ,可以暴力筛求出(这个过程也被称为狄利克雷差分)。
故可以用杜教筛求 f,时间复杂度 O(n2/3logn)。完整代码
int get_f(int n){
if(n <= BOUND) return f[n];
if(mp.count(n)) return mp[n];
int res = Mul(Mul(n, Add(Sub(Mul(n, n), Mul(n, 3)), 2)), inv3);
for(int l=2,r;l<=n;l=r+1){
r = n / (n / l);
res = Sub(res, Mul(r - l + 1, get_f(n / l)));
}
return mp[n] = res;
}
P4318 完全平方数: 求第 n 小的无平方因子数。
先二分,然后变成了求 μ2 的前缀和。这东西有一个非常容易的基于容斥的做法,和本文无关,故不展开,这里仅给出杜教筛做法。
对于 μ2,它的狄利克雷生成函数是什么呢?首先 f(n)=μ2(n) 显然是一个积性函数,因此可以得到:
F(z)=p∈P∏i≥0∑f(pi)p−iz=p∈P∏1+p−z=p∈P∏1−p−z1−p−2z=ζ(2z)ζ(z)
故它的狄利克雷生成函数为 ζ(2z)ζ(z),于是杜教筛可以构造两个函数,g,h,g 的狄利克雷生成函数为 ζ(2z),h 的狄利克雷生成函数为 ζ(z)。
h=1 是显然的,那 g 是什么呢,换句话说 ζ(2z) 是谁的狄利克雷生成函数呢?这是一个值得考虑的问题。
考察 ζ(z) 的一个等价形式:
ζ(z)=p∈P∏i≥0∑p−iz
将 z 替换为 2z,可以得到:
ζ(z)=p∈P∏i≥0∑p−2iz=p∈P∏i≥0∑[2∣i]p−iz
也就是说 g(pi)=[2∣i],又因为 g 应当是一个积性函数,所以自然得出若 n 为完全平方数,则 g(n)=1,否则 g(n)=0。
现在 g,h 都构造好了,要求 f 的前缀和,必须求出 g,h 的前缀和,h 的前缀和是容易的,g 的前缀和就是 ⌊n⌋ 也是容易的。
时间复杂度 O(n2/3logn),完整代码。
int get_f(int n){
if(n <= BOUND) return f[n];
if(mp[n]) return mp[n];
int ans = n;
for(int l=2,r;l<=n;l=r+1){
r = n / (n / l);
int d = sqcnt(r) - sqcnt(l - 1);
if(d) ans -= get_f(n / l) * d; // 注意这里有一个优化,参考 https://lglg.top/598495
}
return mp[n] = ans;
}
P3768 简单的数学题:求 ∑i=1n∑j=1nijgcd(i,j)。
看到这个形式,先进行欧拉反演:
=i=1∑nj=1∑nijgcd(i,j)=i=1∑nj=1∑nijd∣i,d∣j∑φ(d)d=1∑nφ(d)i=1∑⌊dn⌋id2=d=1∑nφ(d)d2(4⌊dn⌋2(⌊dn⌋+1)2)
于是只要求出 φ⋅id2 的前缀和,就可以用整除分块求出答案。
令 f(n)=φ(n)⋅n2,考虑它的狄利克雷生成函数:
F(z)=i≥1∑φ(i)i2⋅i−z=i≥1∑φ(i)i−(z−2)=Φ(z−2)=ζ(z−2)ζ(z−3)
故 F(z)=ζ(z−2)ζ(z−3)。而 ζ(z−3) 是 id3 的生成函数,ζ(z−2) 是 id2 的生成函数。这两个函数的前缀和都是容易的,时间复杂度 O(n2/3),完整代码。
LibreOJ 6682 梦中的数论:求 F(n)=∑i=1n∑j=1n∑k=1n[(j∣i)∧((j+k)∣i)]。
先对内部的二重和式进行化简:
f(i)=j=1∑nk=1∑n[(j∣i)∧((j+k)∣i)]=j∑k=j∑[j∣i∧k∣i]=(2d(i))=2d2(i)−d(i)
那么原式即为:
F(n)=i=1∑nf(i)=21i=1∑nd2(i)−21i=1∑nd(i)
∑i=1nd(i) 可以用整除分块解决,现在的关键问题是求 ∑i=1nd2(i)。
如果你阅读《哈代数论》比较仔细的话,可以看到第 279 页的定理 304 提到了这个函数 d2:
拉马努金定理:
ζ(2z)ζ4(z)=i≥1∑d2(i)i−z
证明的话比较简单,直接推式子即可:
ζ(2z)ζ4(z)=p∈P∏(1−p−z)41−p−2z=p∈P∏(1+p−z)(1−p−z)−3=p∈P∏(1+p−z)i≥0∑(−1)ip−zi(i−3)=p∈P∏(1+p−z)i≥0∑p−zi(ii+2)=p∈P∏1+i≥1∑p−iz((2i+2)+(2i+1))=p∈P∏1+i≥1∑p−iz(i+1)2
所以 ζ(2z)ζ4(z) 是一个积性函数 ξ 的生成函数,且 ξ(pi)=(i+1)2,则 ξ(n) 就是它的唯一分解的次数加一的乘积的平方,也就是 d2(n),证毕。
利用拉马努金定理,我们凑出来了一个类似杜教筛的形式,ζ(2z) 是完全平方数判定函数的生成函数,这是容易求前缀和的。而 ζ4(z)=(ζ2(z))2,即 d∗d 的生成函数,可以使用整除分块套整除分块实现。
时间复杂度 O(n2/3logn)(我是暴力预处理前 O(n2/3) 的 f 值),应该可以做的更好,完整代码。
Powerful Number 筛是杜教筛的扩展。
一个正整数 n 被称为 Powerful Number,当且仅当对于任意一个质数 p,若 p∣n,则 p2∣n。
OEIS A001694 记录了关于 Powerful Number 的更多信息。下面的代码可以用于判定 n 是否为 Powerful Number:
PowerfulNumberQ[n_] := AllTrue[FactorInteger[n][[All, 2]], # >= 2 &]
定理 1:对于任意一个 Powerful Number n,一定存在两个正整数 a,b,使得 a2b3=n。
证明:可以用构造法证明。具体地,取 n 的唯一分解 n=∏piei。对于每一个 piei,若 2∣ei,则令 a←a⋅piei,否则令 b←b⋅pi3,a←a⋅piei−3。由于 ei≥2,所以每一步乘上的都是整数。故存在这样的 a,b。
定理 2:≤n 的 Powerful Number 的数量为 O(n)。
证明:≤n 的 Powerful Number 的精确数量为:
k=1∑⌊n⌋⌊3k2n⌋
适当调整得到:
k=1∑⌊n⌋⌊3k2n⌋∼∫1⌊n⌋⌊3x2n⌋dx∼∫1nx2/3n1/3dx=−33n+3n∼O(n)
在后文中,我们使用 Z+[a2b3] 来表示全体 Powerful Number 集合。
现在我们要求出一个积性函数 f 的前缀和,可以尝试构造两个积性函数 g,h 使得 f=g∗h。进一步地,我们限制 ∀p∈P,f(p)=g(p),也就是在质数取值上,f,g 的值相同。令 F,G,H 分别表示 f,g,h 的前缀和。
根据狄利克雷卷积的定义,对于质数 p,我们有 f(p)=g(p)h(1)+g(1)h(p),又因为 f(p)=g(p),h(1)=g(1)=1,所以 h(p)=0。又因为 h 是积性函数,所以 h(n)=0 是 n 为 Powerful Number 的充分(非必要)条件。
接着我们仿照杜教筛的方法来推式子:
F(n)=i=1∑nf(i)=i=1∑nd∣i∑h(d)g(di)=d=1∑nh(d)i=1∑⌊dn⌋g(i)=d≤n,d∈Z+[a2b3]∑h(d)G(⌊dn⌋)
若 G 容易求前缀和(注意这里如果使用杜教筛求前缀和,则总时间复杂度为 O(n2/3),则只需要暴力枚举 d(由关于 Powerful Number 的定理 2 得知 d 的数量为 O(n)),计算 h(d) 的值即可。
如何求得 h(d) 的值?由于 h 是积性函数,因此可以尝试预处理所有 h(pe) 的值。我们需要预处理出的值的总数为 O(π(n)logn)=O(lognn)。
如果 h(pe) 的值是容易求解的,那么就可以做到 O(n) 了,否则我们可以递推。具体地,根据 f=g∗h 可以得到:
f(pc)=i=0∑ch(pc−i)g(pi)
进而得到:
h(pc)=f(pc)−i=1∑ch(pc−i)g(pi)
这样递推时间复杂度为 O(n),接着暴力搜索 Powerful Number 的过程中就可以算出每一个 Powerful Number 对应的 h 值了。
整理一下,Powerful Number 求积性函数 f 的前缀和的流程如下:
- 找到一个容易求前缀和的积性函数 g。
- 预处理 h(pe) 的值。
- 搜索 Powerful Number,并随之计算对应的 h 值。
- 欲求 F(n),则暴力枚举 ≤n 的所有 Powerful Number d,对 h(d)G(⌊dn⌋) 求和。
时间复杂度预处理 O(n),单次查询 O(n) 或 O(n2/3)(若需要杜教筛)。
P5325 【模板】Min_25 筛:定义积性函数 f 满足 f(pk)=pk(pk−1),求它的前缀和。
若 p 为质数,则 f(p)=p(p−1),我们需要构造一个积性函数 g,使得 f(p)=g(p) 且 g 的前缀和容易求解。那么很容易想到 g(n)=nφ(n)。现在只需要求出 g 的前缀和,就可以用 Powerful Number 筛求出 f 的前缀和。
不妨先求出 g 的狄利克雷生成函数:
G(z)=i≥1∑φ(i)i⋅i−z=i≥1∑φ(i)i−(z−1)=Φ(z−1)=ζ(z−1)ζ(z−2)
而 ζ(z−2),ζ(z−1) 分别为 id2 和 id 的生成函数,这两个数论函数都是容易求前缀和的,所以可以用杜教筛求出 g 的前缀和。
时间复杂度 O(n2/3)。
完整代码。写的比较丑,常数很大,仅作参考。
LibreOJ 6053. 简单的函数:定义积性函数 f 满足 f(pk)=p⊕k,其中 ⊕ 为异或运算,求它的前缀和。
若 p 为质数,则:
f(p)={3p−1p=2otherwise
那么构造积性函数 g 时不妨按照奇偶性分别考虑,对于奇数,可以构造 φ(n),对于偶数,可以构造 3φ(n),也就是:
g(n)={φ(n)3φ(n)2∤n2∣n
那么现在的问题是求 g 的前缀和,记 φ 的前缀和为 Φ(n),则推一下式子:
G(n)=i=1∑ng(i)=i=1∑n[2∣n]3φ(n)+i=1∑n[2∤n]φ(n)=i=1∑n[2∣n]2φ(n)+Φ(n)
令 K(n)=∑i=1n[2∣n]φ(n),则原式为 G(n)=Φ(n)+2K(n),对 K(n) 推式子:
K(n)=i=1∑⌊2n⌋φ(2n)=i=1∑⌊2n⌋φ(n)gcd(n,2)=2i=1∑⌊2n⌋[2∣n]φ(n)+i=1∑⌊2n⌋[2∤n]φ(n)=G(⌊2n⌋)
于是得到 G(n)=2G(⌊2n⌋)+Φ(n)。而 Φ(n) 可杜教筛求得,所以 G(n) 也可以经过 O(logn) 次递归求得。注意时间复杂度单次仍然为 O(logn),只是整体上多了杜教筛的 O(n2/3)。
时间复杂度 O(n2/3)。码力不足,没有码,但愿推导过程没错。
Min_25 筛是一种是埃式筛的扩展,可以以 O(lognn3/4) 的时间复杂度求出积性函数 f(n) 的前缀和,要求:
- 对于质数 p,f(p) 是一个关于 p 的低阶多项式(严格来说应该是完全积性函数线性组合的形式)。
- 对于质数 p,f(pk) 可以快速求值。
可以发现大多数函数都满足这两个限制,因此 Min_25 筛是目前最实用的高级筛法。
- τ(n) 表示 n 的最小质因子。
- pi 表示第 i 小质数,特别地,p0=1。但与一般的质数定义相同地,不认为 1 是质数。
Min_25 筛的第一部分是质数前缀统计问题,即:
给定完全积性函数 f 和整数 n,令 G(n)=∑p∈P,p≤nf(p)。
你需要对于所有 x,求出 G(⌊xn⌋)。
这个问题看起来挺难做的,实际上也挺难做的。不过 min_25 告诉我们可以 dp,设 S(n,m)=∑i=1n[i∈P∨τ(i)>pm]f(i),即质数或最小质因子大于 pm 的函数值前缀和。
那这玩意怎么转移呢?我们考虑埃氏筛的过程,考虑 S(n,m) 到 S(n,m−1) 的变化量,那么肯定是多在了具有 τ(i)=pm 的质因子上。
而这一部分的贡献怎么算?一种看起来比较合适的方法是 f(pm)S(⌊pmn⌋,m−1)(注意这里是 m−1 而不是 m,这是因为一个数可能可以连续除多次 pm)。
但这样子是不对的,因为 S(⌊pmn⌋,m−1) 还包括了全体质数,如果包含了 <pm 的质数,就不满足我们 τ(i)=pm 的要求,所以要将这一部分减去。
可以得到下面的转移方程:
S(n,m)=S(n,m−1)−f(pm)(S(⌊pmn⌋,m−1)−j=1∑m−1f(pj))
值得注意的是,如果 (pm)2>n,那么 ⌊pmn⌋<m,则 S(⌊pmn⌋,m−1)−∑j=1m−1f(pj)=0,因此此时 S(n,m)=S(n,m−1)。
整理一下:
S(n,m)=⎩⎨⎧S(n,m−1)S(n,m−1)−f(pm)(S(⌊pmn⌋,m−1)−j=1∑m−1f(pj))(pm)2>notherwise
边界 S(n,0)=∑i=2nf(i)。
最后我们来着手解决原问题。设 ps(n) 表示最小的大于等于 n 的质数,那么不可能存在合数 k≤n,使得 τ(k)>ps(n),所以 S(n,s(n)) 就是答案 G(n)。
具体的实现方法会在随后的一道例题中给出。
分析一下时间复杂度,额,我好像一顿积分后没有分析出来,解析解带有一个 ExpIntegralEi 的东西,不是很会处理。
不过记住这个东西的时间复杂度是 O(lognn3/4) 的就行了。
LibreOJ 6235 区间素数个数 给定 n,求 π(n)。
构造完全积性函数 f(n)=1,然后沿用质数前缀统计的思路即可。时间复杂度 O(lognn3/4)。
可是大家不想看看具体怎么实现吗?
首先由于第二维最大可以达到 π(n),于是需要筛 [1,n] 的质数。
void sieve(int n){
for(int i=2;i<=n;i++){
if(!vis[i]) pri[++tot] = i;
for(int j=1;j<=tot&&1ll*i*pri[j]<=n;j++){
vis[i * pri[j]] = 1;
if(!(i % pri[j])) break;
}
}
}
sqn = sqrt(n);
sieve(sqn);
然后注意到由于经典结论 ⌊b⌊an⌋⌋=⌊abn⌋,故第一维实际上只有形如 ⌊xn⌋ 的值是有用的,这样的值只有 O(n) 个。可以用你喜欢的方法(比如整除分块)求出来。由于这一维我们需要记录到 dp 数组中,所以可以提前从大到小标号(需要支持标号与原数互相转换,但是使用 map
太慢,可以根号分治,用两个数组记录)方便后面的处理:
int get(i64 x){ return x <= sqn ? rev[x] : revt[n / x]; }
for(i64 l=1,r;l<=n;l=r+1){
r = n / (n / l), w[++wtt] = n / l;
if(w[wtt] <= sqn) rev[w[wtt]] = wtt;
else revt[n / w[wtt]] = wtt;
}
最后的 dp 只需要按照我们上面推导的式子做即可,注意可以滚动数组省掉第二维:
for(int i=1;i<=wtt;i++) f[i] = w[i] - 1;
for(int j=1;j<=tot;j++){
for(int i=1;i<=wtt&&1ll*pri[j]*pri[j]<=w[i];i++) f[i] -= f[get(w[i] / pri[j])] - (j - 1);
}
cout << f[1] << '\n';
完整代码。
接下来进入 Min_25 筛的第二部分。
刚刚我们解决了质数前缀统计问题,不过可惜的是它只能解决完全积性函数,而我们只要求质数的取值是一个低次多项式。不过稍作转化并不是难事,对于多项式 ∑i=0naixi,完全可以将 xi 看成一个(完全)积性函数,然后将每一个这样的 xi 的和计算出来,最后乘上系数相加即可。
于是我们可以求出 Fp(n) 表示 ≤n 的质数 p 的 f(p) 的值之和,现在需要求出积性函数前缀和。
Min_25 告诉我们可以沿用以前的思路,具体地,设 S(n,m)=∑i=2n[τ(i)>pm]f(i),即最小质因子大于 pm 的函数值和。为了利用之前的质数前缀统计,我们可以将质数和合数分开计算。
对于质数,我们可以发现答案就是 Fp(n)−Fp(pm),即 >pm 的全体质数的函数值和。
对于合数,这有点复杂,不过我们可以尝试枚举它的最小质因子和次数:
k∈[m+1,π(n)]pk2≤n∑e=1∑⌊logpkn⌋f(pke)(S(⌊pken⌋,k)+[e>1])
稍微解释一下这个二重和式。我们枚举最小质因子 pk 和次数 e 后,pke 就和剩下的数互质了,因此根据积性函数性质,可以提出一个 f(pke),剩余的部分就自然是 S(⌊pken⌋,k) 了。
那 [e>1] 又是什么呢?我们发现 S(n,m) 中是不包括 f(1)=1 的,而单独的 pke 在 e>1 处又是应该计入答案的(注意 e=1 时 pke=pk 是质数而不是合数,不计入答案),所以需要额外补一下。
不妨整理一下:
S(n,m)=⎩⎨⎧0Fp(n)−Fp(pm)+k∈[m+1,π(n)]pk2≤n∑e=1∑⌊logpkn⌋f(pke)(S(⌊pken⌋,k)+[e>1])pm>notherwise
于是我们好像就做完了!最后答案是 S(n,0)+1(记得加上最后的 f(1)=1)。
可以证明这一段的时间复杂度仍然是 O(lognn3/4)。
P5325 【模板】Min_25 筛:定义积性函数 f 满足 f(pk)=pk(pk−1),求它的前缀和。
第一道 Min_25 筛题,侧重讲解一下如何实现。
首先很容易发现 f(p)=p2−p,满足 Min_25 的适用条件,将 f(p) 拆成两个完全积性函数之差 id2,id 先去做质数前缀统计,这一部分和区间素数个数一题类似,因此只对关键部分做出说明:
void sieve(int n){
for(int i=2;i<=n;i++){ // 线性筛质数
if(!vis[i]) pri[++tot] = i;
for(int j=1;j<=tot&&1ll*i*pri[j]<=n;j++){
vis[i * pri[j]] = 1;
if(!(i % pri[j])) break;
}
}
for(int i=1;i<=tot;i++){ // 预处理小范围的函数前缀和
fp1[i] = Add(fp1[i - 1], pri[i]);
fp2[i] = Add(fp2[i - 1], Mul(pri[i], pri[i]));
}
}
int get(i64 x){ return x <= sqn ? rev[x] : revt[n / x]; } // 定位函数
sqn = sqrt(n);
sieve(sqn);
for(i64 l=1,r;l<=n;l=r+1){ // 找到所有形如 floor(n/x) 的数,并建立索引
r = n / (n / l), w[++wtt] = n / l;
if(w[wtt] <= sqn) rev[w[wtt]] = wtt;
else revt[n / w[wtt]] = wtt;
}
for(int i=1;i<=wtt;i++){ // 准备初始值
int wt = w[i] % mod;
f1[i] = Sub(Mul(wt, Add(wt, 1), inv2), 1); // 记得减去 1
f2[i] = Sub(Mul(wt, Add(wt, 1), Add(wt, wt, 1), inv6), 1);
}
for(int j=1;j<=tot;j++){ // 质数前缀统计形式的 dp
for(int i=1;i<=wtt&&1ll*pri[j]*pri[j]<=w[i];i++){
f1[i] = Sub(f1[i], Mul(pri[j], Sub(f1[get(w[i] / pri[j])], fp1[j - 1])));
f2[i] = Sub(f2[i], Mul(pri[j], pri[j], Sub(f2[get(w[i] / pri[j])], fp2[j - 1])));
}
}
接下来就是第二部分的 dp 了,我们直接枚举 k,e(e 只需要隐式枚举即可,可以显式枚举 pke),然后计算贡献。
int solve(i64 n, int m){
if(pri[m] > n) return 0; // 特判一下
int ans = Sub(Sub(f2[get(n)], fp2[m]), Sub(f1[get(n)], fp1[m])); // 计算质数处的取值
for(int k=m+1;k<=tot&&1ll*pri[k]*pri[k]<=n;k++){
bool fir = 1; // 标记一下 e == 1 是否成立
for(i64 pw=pri[k],pwt=pri[k];pw<=n;pw*=pri[k],pwt=Mul(pwt,pri[k])){
// 注意 solve(n / pw, k),我们直接递归就好了
ans = Add(ans, Mul(Mul(pwt, Sub(pwt, 1)), Add(solve(n / pw, k), !fir)));
fir = 0;
}
}
return ans;
}
完整代码。
SP34096 DIVCNTK - Counting Divisors (general):给定 k,计算 f(n)=d(nk) 的前缀和。
对于质数 p,f(p)=d(pk)=k+1,f(pe)=d(pke)=ke+1 满足 Min_25 的适用条件,那么只需要证明 f 是积性函数即可。
所幸这也是容易的,若 gcd(a,b)=1,则 gcd(ak,bk)=1,而 d 是积性函数,故 f 亦是积性函数。
所以可以大胆用 Min_25 筛完成,时间复杂度 O(lognn3/4),完整代码。
UOJ 188 【UR #13】Sanrd:定义 f(n) 表示 n 的“非严格次大质因子”(具体可以参考原题面,若不存在则为 0),求 f 的前缀和。
注意,本题中的 f(n) 的定义可以参考下面的 Wolfram 代码:
SecondLargestPrimeFactor[n_] := Module[{factors},
factors = Flatten[ConstantArray @@@ FactorInteger[n]];
If[Length[factors] < 2, 0, (Reverse @ Sort[factors])[[2]]]
]
考虑到 f 不是积性函数,所以不能直接套用 Min_25 筛积性函数的方法,不过作为一种强有力的工具,Min_25 仍然可以帮助我们分析。
考虑第二部分,我们同样设 S(n,m) 表示 S(n,m)=∑i=2n[τ(i)>pm]f(i),现在的问题是如何转移?
对于质数部分,答案显然是 0。对于合数部分,当我们剥离出最小质因子及其次数 pe 后,如果它是次大质因子,那么会贡献 pmax(π(⌊pen⌋))−π(p−1)),即 p∼pen 的所有质数都可以作为剩余部分。如果不是次大质因子,那么就直接递归,没有额外贡献。而 π 是质数前缀统计,也是可以 Min_25 的。时间复杂度 O(lognn3/4)。
完整代码。