Yuhang's Blog

质数的线性筛法和积性函数值的计算

2021-02-28 CodingMathematics

  1. 1. 数学背景
  2. 2. 质数的线性筛
  3. 3. 积性函数值的计算
    1. 3.1. 欧拉函数的计算
    2. 3.2. 莫比乌斯函数的计算
    3. 3.3. 除数函数的计算

质数的线性筛法比埃氏筛法复杂度更优,且可以被用来求积性函数的函数值。本文给出了相应的数学背景和代码实现,给出了三个常用积性函数(欧拉函数、莫比乌斯函数和除数函数)的线性筛法求法。

数学背景

对任意大于1的自然数n进行质因数分解: n=ipiei 均存在最小的pi,设为p1。则:

  • p1n最小的质因数,也是n最小的除1以外的因数(最小非平凡因数);
  • n/p1n最大的除n以外的因数(最大非平凡因数);
  • 当且仅当p1=n,或者n/p1=1n是质数。
  • n/p1的最小的质因数不小于p1,当且仅当p1n的质因数分解中对应的次数e1满足e12n/p1的最小的质因数等于p1。这个结论的证明可以考虑将n的所有质因数放在一个可重集合S中,该可重集合的最小元素是p1;而n/p1的所有质因数构成的可重集合SS去掉一个p1,则显然S的最小元素不小于p1。当n/p1的最小质因数小于p1时,显然gcd(n/p1,p1)=1

质数的线性筛

现在我们转换一下视角,把重点放在上面的n/p1,即n的最大非平凡因数上。对于每个大于1的自然数i,枚举所有的自然数n,使得in的最大非平凡因数。这需要i乘上一个质数pj,其中pj扮演的角色是n的最小质因数,来实现。但这个质数pj是有范围限制的,因为根据上面的讨论,pj不应当超过i的最小质因数。由于i大于1,被枚举到的n显然是合数。而且由于这种枚举方式的唯一性(最大非平凡因数乘以最小质因数),每个合数n只会被枚举到1次。这样的筛法是最优的线性时间复杂度,我们称之为线性筛。 在上面的算法中,在遍历i的过程中,我们需要维护一个不超过i的质数列表,这样才能枚举pj。(因为pj不超过i的最小质因数,显然有pji,当且仅当i是质数取等号。)

重新整理一下思路:枚举所有的i2。如果i此前没有被标记过,则它是一个质数,将其加入到我们维护的质数列表中。然后从小到大遍历质数列表中的所有质数pj,将n=i×pj标记为合数。当pj|i时,pji的最小质因数,此时应标记最后一个n并停止循环。

代码实现如下:

1
2
3
4
5
6
7
8
9
10
11
const int N = 1e7+5;
short flg[N]; int p[N], tot;
void seive(int n) {
rep(i,2,n) {
if (!flg[i]) p[++tot]=i;
for(int j=1; j<=tot && i*p[j]<=n; ++j) {
flg[i * p[j]] = 1;
if (i % p[j] == 0) break;
}
}
}

积性函数值的计算

与线性筛的过程契合,在线性筛中求积性函数值一般需要考虑函数在以下三种情况的值,其中后两种考虑nn/p1的递推关系。 f(n)={Case 1n is prime,Case 2p1n/p1,Case 3p1n/p1. 实际上第一种情况往往显然;第二种情况可以根据积性函数的性质得到(由于互质关系)f(n)=f(p1)f(n/p1),只有第三种情况需要额外考虑。

欧拉函数的计算

已经知道 ϕ(n)=ipiei(11pi)=ni(11pi)n是质数时, ϕ(n)=n1p1n/p1时, ϕ(n)=ϕ(n/p1)(p11)p1n/p1时, ϕ(n)=ϕ(n/p1)p1 代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const int N = 1e7+5;
int phi[N]; short flg[N]; int p[N], tot;
void getPhi(int n) {
phi[1] = 1;
rep(i,2,n) {
if (!flg[i]) phi[i] = i-1, p[++tot]=i;
for(int j=1; j<=tot && i*p[j]<=n; ++j) {
flg[i * p[j]] = 1;
if (i % p[j] == 0) {
phi[i * p[j]] = p[j] * phi[i];
break;
} else {
phi[i * p[j]] = phi[p[j]] * phi[i];
}
}
}
}

莫比乌斯函数的计算

已经知道 μ(n)={1n=1,(1)kn is a product of k distinct primes,0otherwise.n是质数时, μ(n)=1p1n/p1时, μ(n)=μ(n/p1)p1n/p1时, μ(n)=0 代码实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const int N = 1e7+5;
short mu[N]; short flg[N]; int p[N], tot;
void getMu(int n) {
mu[1] = 1;
rep(i,2,n) {
if (!flg[i]) mu[i] = -1, p[++tot]=i;
for(int j=1; j<=tot && i*p[j]<=n; ++j) {
flg[i * p[j]] = 1;
if (i % p[j] == 0) {
mu[i * p[j]] = 0;
break;
} else {
mu[i * p[j]] = -mu[i];
}
}
}
}

除数函数的计算

如果是计算除数的和:

已经知道 σ(n)=i(1+pi+pi2++piei)g(n)=1+p1+p12++p1e1n是质数时, g(n)=1+nσ(n)=1+np1n/p1时,即e1=1g(n)=1+p1σ(n)=σ(p1)σ(n/p1) 第二式由σ的积性可得。 当p1n/p1时,即e12g(n)=g(n/p1)p1+1σ(n)=σ(n/p1)g(n)g(n/p1)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const int N = 1e7+5;
int sigma[N], g[N]; short flg[N]; int p[N], tot;
void getMu(int n) {
sigma[1] = 1;
rep(i,2,n) {
if (!flg[i]) sigma[i]=g[i]=i+1, p[++tot]=i;
for(int j=1; j<=tot && i*p[j]<=n; ++j) {
flg[i * p[j]] = 1;
if (i % p[j] == 0) {
g[i * p[j]] = g[i] * p[j] + 1;
sigma[i * p[j]] = sigma[i] / g[i] * g[i * p[j]];
break;
} else {
g[i * p[j]] = p[j]+1;
sigma[i * p[j]] = sigma[i] * sigma[p[j]];
}
}
}
}

如果计算除数的个数,思路类似:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const int N = 1e7 + 5;
short flg[N]; int p[N], tot;
int d[N]; int g[N];
void init(int n) {
d[1] = 1;
rep(i,2,n) {
if (!flg[i]) d[i] = g[i] = 2, p[++tot] = i;
for(int j=1; j<=tot && i*p[j]<=n; j++) {
flg[i * p[j]] = 1;
if (i % p[j] == 0) {
g[i * p[j]] = g[i] + 1;
d[i * p[j]] = d[i] / (g[i]) * (g[i * p[j]]);
break;
} else {
g[i * p[j]] = 2;
d[i * p[j]] = d[i] * d[p[j]];
}
}
}
}