专栏文章
对质数以及一些筛法的研究
个人记录参与者 1已保存评论 0
文章操作
快速查看文章及其快照的属性,并进行相关操作。
- 当前评论
- 0 条
- 当前快照
- 1 份
- 快照标识符
- @minw0vxq
- 此快照首次捕获于
- 2025/12/02 09:16 3 个月前
- 此快照最后确认于
- 2025/12/02 09:16 3 个月前
数论是数学对正整数进行研究的分支。筛法最初起源于找出质数的过程中。以下将浅谈数论中各种各样的筛法以及它们的应用。
质数,是指除了 和本身之外不能被任何正整数整除的数,即恰好只有两个因数。于是不难写出以下判断代码:
CPPbool isPrime(int x){
for(int i=2;i<=x;i++)
if(x%i==0)
return false;
return true;
}
观察上面这段代码,会发现它有很大的冗余。由于因子总是成对出现的,故如果不能被较小的因子整除,那么也一定不能被较大的因子整除。于是只需要枚举不超过 的数即可。
CPPbool isPrime(int x){
for(int i=2;i*i<=x;i++)
if(x%i==0)
return false;
return true;
}
但如果判断很多个数是否为质数,或者求一段范围内的质数,这种方法就显得有点无力了。于是就有了筛法。
按照质数的定义,含有除 和自己之外的因子的数不是质数,那么我们不妨用每个数筛掉它的倍数,最后剩下的就是质数。不难写出如下代码,时间复杂度 :
CPPfor(int i=2;i<=n;i++)
for(int j=i*2;j<=n;j+=i)
isPrime[j]=false;
这样做不够快。在这份代码中,一个合数不仅会被质因子筛掉,还会被质因子的倍数筛掉,是没有必要的。于是我们只需枚举质数的倍数即可。得到的是 的埃氏筛:
CPPfor(int i=2;i<=n;i++)
if(isPrime[i])
for(int j=i*2;j<=n;j+=i)
isPrime[j]=false;
还可以更快吗?一个合数可能有多个不同的质因子,会被多次筛掉,如果它只会被最小的质因子筛掉,就可以有效优化复杂度,于是有了以下代码:
CPPfor(int i=2;i<=n;i++){
if(isPrime[i])Prime[++tot]=i;
for(int j=1;j<=tot&&i*Prime[j]<=n;j++){
isPrime[i*Prime[j]]=false;
if(i%Prime[j]==0)break;//此时说明 prime_j 已经不是 i 的最小质因数,直接退出即可
}
}
这样做能够保证每个合数只被它的最小质因数筛掉,得到的就是时间复杂度 的欧拉筛。
欧拉筛在求积性函数方面有非常重要的作用。以下补充一些积性函数相关的定义:
- 定义域为正整数集,值域为复数集的子集的函数称为数论函数。
- 满足 的数论函数称为积性函数。
- 满足 的数论函数称为完全积性函数。
以下以求 为例介绍欧拉筛在求积性函数中的应用。 定义为 的正整数中与 互质的数的个数,即 。显然欧拉函数是积性函数,证明略。
首先考虑单个数 的欧拉函数怎么求,设 的质因数分解的结果为 ,那么与 互质的条件是不含有任何一个相同的质因子,所以 ,就可以用一开始的算法 求欧拉函数。
CPPint Phi(int n){
int phi=n;
for(int i=2;i*i<=n;i++)
if(n%i==0){
phi=phi/i*(i-1);
while(n%i==0)
n/=i;
}
return phi;
}
那么如果要求 的欧拉函数呢?一个一个求总复杂度将会达到 ,不能接受。考虑使用欧拉筛求解,分类讨论:
-
,此时根据积性函数的定义,。
-
否则,说明 包含质因子 ,那么括号内的部分不变,只是前面的 的部分多乘上了 ,。
于是就可以在 的时间复杂度求出 。
CPPint Euler_Sumphi(int n){
phi[1]=1;
for(int i=2;i<=n;i++){
if(isPrime[i])Prime[++tot]=i,phi[i]=i-1;
for(int j=1;j<=tot&&i*Prime[j]<=n;j++){
isPrime[i*Prime[j]]=false;
if(i%Prime[j]==0){
phi[i*Prime[j]]=phi[i]*Prime[j];
break;
}
phi[i*Prime[j]]=phi[i]*(Prime[j]-1);
}
}
int sum=0;
for(int i=1;i<=n;i++)
sum+=phi[i];
return sum;
}
但是当 逐渐变大时,这样做也不够快了。有没有更快的办法?有的,这就是杜教筛。以下先普及狄利克雷卷积和莫比乌斯反演。
狄利克雷卷积:对于数论函数 ,定义 为 的狄利克雷卷积,记为 。满足交换律,结合律。
\begin{array}{l}
1,n=1,\\
(-1)^k,n=p_1p_2\cdots p_k,p 为不同质数,\\
0,\text{Otherwise}
\end{array}
\right.
莫比乌斯反演:
证明:由 ,,得 ,得证。
欧拉函数补充性质:。证明:设 ,则有 ,设 表示 的 的个数,那么显然 ,注意到 ,因此 ,由对称性得 ,证毕。
接下来推导杜教筛公式。假设要求 。
考虑求狄利克雷卷积前缀和:
\sum_{i=1}^n (f*g)(i)&=\sum_{i=1}^n \sum_{d|i}f(d)g(\frac{n}{d})\\
&=\sum_{d=1}^ng(d)\sum_{i=1}^{\lfloor \frac{n}{d} \rfloor} f(i)\\
&=\sum_{d=1}^n g(d)S(\lfloor \frac{n}{d} \rfloor)
\end{align*}
移项得
有了上面这些,就可以开始推式子了。由 ,令 ,则 ,第二部分只需要对 整除分块即可。此时时间复杂度 。
CPPint Dujiao_Sumphi(int n){
int sumphi=n*(n+1)/2;
for(int l=2,r;l<=n;l=r+1){
r=n/(n/l);
sumphi-=(r-l+1)*Dujiao_Sumphi(n/l);
}
return sumphi;
}
如果预处理 的 ,那么时间复杂度可以优化到 。优化后的代码如下:
CPPint Dujiao_Sumphi(int n){
int sumphi=n*(n+1)/2;
if(n<=N)return Pre[N];//N=n^{2/3}
for(int l=2,r;l<=n;l=r+1){
r=n/(n/l);
sumphi-=(r-l+1)*Dujiao_Sumphi(n/l);
}
return sumphi;
}
对于其它积性函数的求和,也可以通过构造适当的 从而简化公式右边的计算。当然还有其它不错的解法比如 min25 筛,洲阁筛等,将会在后面继续学习探讨。
相关推荐
评论
共 0 条评论,欢迎与作者交流。
正在加载评论...