质数的线性筛法比埃氏筛法复杂度更优,且可以被用来求积性函数的函数值。本文给出了相应的数学背景和代码实现,给出了三个常用积性函数(欧拉函数、莫比乌斯函数和除数函数)的线性筛法求法。
数学背景
对任意大于的自然数进行质因数分解: 均存在最小的,设为。则:
- 是最小的质因数,也是最小的除以外的因数(最小非平凡因数);
- 是最大的除以外的因数(最大非平凡因数);
- 当且仅当,或者,是质数。
- 的最小的质因数不小于,当且仅当在的质因数分解中对应的次数满足时的最小的质因数等于。这个结论的证明可以考虑将的所有质因数放在一个可重集合中,该可重集合的最小元素是;而的所有质因数构成的可重集合是去掉一个,则显然的最小元素不小于。当的最小质因数小于时,显然。
质数的线性筛
现在我们转换一下视角,把重点放在上面的,即的最大非平凡因数上。对于每个大于的自然数,枚举所有的自然数,使得是的最大非平凡因数。这需要乘上一个质数,其中扮演的角色是的最小质因数,来实现。但这个质数是有范围限制的,因为根据上面的讨论,不应当超过的最小质因数。由于大于,被枚举到的显然是合数。而且由于这种枚举方式的唯一性(最大非平凡因数乘以最小质因数),每个合数只会被枚举到次。这样的筛法是最优的线性时间复杂度,我们称之为线性筛。 在上面的算法中,在遍历的过程中,我们需要维护一个不超过的质数列表,这样才能枚举。(因为不超过的最小质因数,显然有,当且仅当是质数取等号。)
重新整理一下思路:枚举所有的。如果此前没有被标记过,则它是一个质数,将其加入到我们维护的质数列表中。然后从小到大遍历质数列表中的所有质数,将标记为合数。当时,是的最小质因数,此时应标记最后一个并停止循环。
代码实现如下:
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; } } }
|
积性函数值的计算
与线性筛的过程契合,在线性筛中求积性函数值一般需要考虑函数在以下三种情况的值,其中后两种考虑和的递推关系。 实际上第一种情况往往显然;第二种情况可以根据积性函数的性质得到(由于互质关系),只有第三种情况需要额外考虑。
欧拉函数的计算
已经知道 当是质数时, 当时, 当时, 代码实现:
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]; } } } }
|
莫比乌斯函数的计算
已经知道 当是质数时, 当时, 当时, 代码实现如下:
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]; } } } }
|
除数函数的计算
如果是计算除数的和:
已经知道 记 当是质数时, 当时,即, 第二式由的积性可得。 当时,即,
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]]; } } } }
|