素数的筛法

素数的筛法有不少种算法

在此给出常见的三种方法函数

 

如下给出的全部代码均已经过这里的测试测试

 

埃拉托斯特尼筛法

名字好长 :joy:  不过代码很短优化

思路很是简单,对于每个素数,枚举它的倍数,它的倍数必定不是素数spa

这样必定能够保证每一个素数都会被筛出来code

还有,咱们第一层循环枚举到$\sqrt(n)$就好,由于若是当前枚举的数大于n,那么它能筛出来的数必定在以前就被枚举过blog

好比说:get

$\sqrt(100)=10$it

不难发现咱们从$20$枚举所筛去的数必定被$5$筛过io

 1 #include<cstdio>
 2 #include<cmath>
 3 using namespace std;
 4 const int MAXN=10000001;
 5 inline int read()
 6 {
 7     char c=getchar();int f=1,x=0;
 8     while(c<'0'||c>'9')    {if(c=='-')    f=-1;c=getchar();}
 9     while(c>='0'&&c<='9')    x=x*10+c-48,c=getchar();return x*f;
10 }
11 int vis[MAXN];
12 int n,m;
13 int main()
14 {
15     n=read();m=read();
16     vis[1]=1;//1不是质数
17     for(int i=2;i<=sqrt(n);i++)
18         for(int j=i*i;j<=n;j+=i)
19             vis[j]=1;
20     while(m--)
21     {
22         int p=read();
23         if(vis[p]==1)    printf("No\n");
24         else             printf("Yes\n");
25     }
26     return 0;
27 }

 

可是你会发现这份代码只能得30分

看来这种算法仍是不够优秀

下面咱们来探索一下他的优化

另外,这种算法的时间复杂度:$O(n*logn)$

 

埃拉托斯特尼筛法优化版

根据惟一分解定理 

每个数均可以被分解成素数乘积的形式

那咱们枚举的时候,只有在当前数是素数的状况下,才继续枚举就好

这样能够保证每一个素数都会被筛出来

 

 1 #include<cstdio>
 2 #include<cmath>
 3 using namespace std;
 4 const int MAXN=10000001;
 5 inline int read()
 6 {
 7     char c=getchar();int f=1,x=0;
 8     while(c<'0'||c>'9')    {if(c=='-')    f=-1;c=getchar();}
 9     while(c>='0'&&c<='9')    x=x*10+c-48,c=getchar();return x*f;
10 }
11 int vis[MAXN];
12 int n,m;
13 int main()
14 {
15     n=read();m=read();
16     vis[1]=1;//1不是质数
17     for(int i=2;i<=sqrt(n);i++)
18         if(vis[i]==0)
19             for(int j=i*i;j<=n;j+=i)
20                 vis[j]=1;
21     while(m--)
22     {
23         int p=read();
24         if(vis[p]==1)    printf("No\n");
25         else             printf("Yes\n");
26     }
27     return 0;
28 }

 

 

 

 

果真,加了优化以后这种算法快了很多

能够证实,它的复杂度为:$O(n*log^{logn})$

这种算法已经很是优秀了,可是对于1e7这种极端数据,仍是有被卡的风险

那么,还有没有更快的筛法呢?

答案是确定的!

 

欧拉筛

咱们思考一下第二种筛法的运算过程

不难发现,对于6这个数,它被2筛了一次,又被3筛了一次

第二次筛显然是多余的,

咱们考虑去掉这步运算

 1 #include<cstdio>
 2 #include<cmath>
 3 using namespace std;
 4 const int MAXN=10000001;
 5 inline int read()
 6 {
 7     char c=getchar();int f=1,x=0;
 8     while(c<'0'||c>'9')    {if(c=='-')    f=-1;c=getchar();}
 9     while(c>='0'&&c<='9')    x=x*10+c-48,c=getchar();return x*f;
10 }
11 int vis[MAXN],prime[MAXN];
12 int tot=0;
13 int n,m;
14 int Euler()
15 {
16     vis[1]=1;
17     for(int i=2;i<=n;i++)
18     {
19         if(vis[i]==0)    prime[++tot]=i;
20         for(int j=1;j<=tot&&i*prime[j]<=n;j++)
21         {
22             vis[i*prime[j]]=1;
23             if(i%prime[j]==0)    break;
24         }
25     }
26 }
27 int main()
28 {
29     n=read();m=read();
30     Euler();
31     for(int i=1;i<=m;i++)
32     {
33         int p=read();
34         if(vis[p]==1)    printf("No\n");
35         else             printf("Yes\n");
36     }
37     return 0;
38 }

 

对于这份代码,咱们分状况讨论

 

当$i$是素数的时候,那么两个素数的乘积必定没有被筛过,能够避免重复筛

 

当$i$不是素数的时候

程序中有一句很是关键的话

if(i%prime[j]==0) break;

 

 

若是咱们把$i$的惟一分解形式表示为$i = p_1^{a_1}p_2^{a_2} \dots p_n^{a_n}$

这句话能够保证:本次循环只能筛除不大于${p_1}*i$的数

这样的话每一个数$i$都只能筛除不大于$i$乘$i$的最小素因子的数

反过来,每一个数只能被它的最小素因子筛去。

也就能够保证每一个数只会被筛一次(这一步好像不是很显然,我在最后会给出证实)

举个例子,

设$i=2*3*5$,此时能筛去$i*2$,可是不能筛去$3*i$

由于若是能晒出$3*i$的话,

当$i_2=3*3*5$时,筛除$2*i_2$就和前面重复了

另外为了方便你们直观理解,给出一张图表

 

 

这样显得直观一些

你们好好揣摩揣摩

 

上面的证实:我本身瞎yy的可能不是很严谨

如今咱们须要证实$i = p_1^{a_1}p_2^{a_2} \dots p_n^{a_n}$只会被$p_1$筛去

那么咱们须要证实三个条件

1.$i$必定被$p_1$和$p_1^{a_1 - 1}p_2^{a_2} \dots p_n^{a_n}$筛除过

很显然,在枚举到$p_1$以前不会有其余素因子使$p_1^{a_1 - 1}p_2^{a_2} \dots p_n^{a_n}$中止循环

2.$i$不会被$p_1^{a_1}p_2^{a_2 - 1} \dots p_n^{a_n}$筛去

一样也很显然,当枚举到$p_1$时就会中止循环

 

 

 

能够看出这种算法的时间效率是很是高的!

时间复杂度:严格$O(n)$

 

总结

在通常状况下,第二种筛法已经彻底够用。

第三种筛法的优点不只仅在于速度快,并且还可以筛积性函数,像欧拉函数,莫比乌斯函数等。

这个我之后还会讲的

相关文章
相关标签/搜索