欧拉筛法
欧拉筛法是用线性时间求取给定范围内的素数的个数,当然素数的值也是可以记录的,适用于大范围性的素数求取,如果只是判断某一个素数,欧拉筛就显得大材小用了(费空间资源),这时候可以考虑Miller-Rabin 素数测试。
欧拉函数的时间复杂度趋于O(N)的,即使是1e8也不在话下
讲解欧拉筛之前回顾一下埃拉托斯特尼筛法
埃拉托斯特尼筛法:原理就是先找出一个素数然后把这个素数的倍数都标记为合数(一个正整数的所有倍数一定是合数,除了1),在下一次遇到被标记的数时直接跳过。
例如:
第一次遍历遇到素数2,所以(4,6,8,10…)被标记为合数
第二次遍历遇到素数3,所以(9,12,15…)被标记为合数
第三次遍历遇到合数4,因为已经被2的倍数标记所以直接跳过。
然后按照上面方法一直遍历到最后就筛选出了所有素数
…
代码
#include<iostream>
#include<cstring>
#include<cmath>
using namespace std;
int prime[1000000];
/*埃拉托斯特尼筛法*/
bool vis[100000001];//1表示访问过,0表示没有访问过
int main()
{
memset(prime,0,sizeof(prime));
memset(vis,false,sizeof(vis));
int n;
scanf("%d",&n);
vis[1]=true;
for(int i=2;i<=n;i++)
{
if(!vis[i])//是素数
{
prime[++prime[0]]=i;
vis[i]=true;
for(int j=i*i;j<=n;j+=i)
{
vis[j]=true;
}
}
}
printf("%d",prime[0]);
return 0;
}
注意:上面是我对埃拉托斯特尼筛法做了一定修改,便于后面理解欧拉筛。(望dalao勿吐槽)
但是埃拉托斯特尼筛法的时间复杂度O(nlognlogn)显然对于1e8的数据是会超时的,其实筛选的过程中做了重复的操作,比如12这个数是2的倍数,也是3的倍数,就被2和3的倍数标记了两次,后面较大的数被重复标记的次数更多,为了解决重复标记的问题,于是欧拉筛排上用场了。
欧拉筛
先上代码,方便讲解
#include<iostream>
#include<cstring>
using namespace std;
const int Maxn=1e8+1;
const int inf=1e6;
int prime[100000];//存素数
bool vis[100000001];//0代表没有访问过,1访问过
int main()
{
int n;
memset(prime,0,sizeof(prime));
memset(vis,false,sizeof(vis));
scanf("%d",&n);
vis[1]=true;
for(int i=2;i<=n;i++)
{
if(!vis[i])//是素数,没被访问的都是,说明没有被标记为合数
{
vis[i]=true;
prime[++prime[0]]=i;
}
for(int j=1;j<=prime[0]&&i*prime[j]<=n;j++)//以i为倍数进行筛除,对所有已知素数的倍数(也就是合数)
{
vis[prime[j]*i]=true;//筛除
if(i%prime[j]==0) break;
}
}
printf("%d",prime[0]);
return 0;
}
首先解释为什么要j从1开始并且j<=prime[0]
原因:因为我们要对每一个已求出来的素数的倍数标记为合数,倍数为i,因为i会一直从2到n。
说到这里有人会问,怎么确定它就能保证不重复标记合数
这就是我们这里if(i%prime[j]==0) break;
的作用,如果不退出循环当我们继续遍历到i*prime[j+1]的时候,因为i%prime[j]=0,故i=t倍prime[j],所以i*prime[j+1]=t * prime[j] * prime[j+1],相当于是prime[j]的倍数(已经被标记过),所以这样就会造成重复标记,因此我们跳出循环。
不会重复标记我们已经证明了
接下来有一个很头痛的事情就是你怎么知道不会遗漏标记呢?(我也是想了很久,才明白,本蒟蒻太痛苦了)
举个例子:在遍历标记合数是prime[3]也就是5是从==i=5(5的5倍开始)==开始标记的(prim[j]的倍数标记始终是从i=prime[j]开始的,也就是i>=prime[j]),那么5的四倍,三倍,两倍不就没有标记到吗?
(其实一点就通)分析:我们知道任何一个正整数都是可以由若干个素数的幂次的乘积构成(质因数分解),所以如果i<prime[j],那么i*prime[j]这个数一定是其他更小的素数的倍数,所以已经被标记,故我们不需要再次标记,所以这样就能够解释上面素数5的例子了。
趁热打一下铁吧
洛谷 P3912 素数个数
如果有分析不对的地方,还望dalao指出