素数筛法小结

素数筛法一直是我前期学习的难题,现在把它总结一下,防止忘记。

① 普通筛法 O(n√n)

根据定义,一个合数n一定可以由两个数相乘得到,这两个因数一个大于√n,另一个小于√n,所以可以对因数从2到√n进行枚举,判断是否可以被n整除,如无法整除,则为素数。

② 埃氏筛法 O(n㏒n)

如果一个数是素数,那么他的倍数就一定是合数,就可以在给定范围内将他的倍数先筛掉,所以我们可以开一个标记数组,先假定开始所有数的状态都是素数(即未被标记)。首先我们从2到n开始枚举,如未被标记,则该数为素数,我们再将该数在n内的所有倍数筛掉(标记)。

#include<bits/stdc++.h>
using namespace std;
int prime[50005],//储存素数
    flag[50005],//标记数组
    cot;//计数
int main()
{
    int n;
    cin>>n;
    for(int i=2;i<=n;i++)
    {
       if(!flag[i])//如未被标记,即是素数
       {
         prime[cot++]=i;//加入素数数组中
         for(int j=2*i;j<=n;j+=i)//将倍数都标记
         {
           flag[j]=1;
         }
       }
    }
//    printf("%d\n",cot);
//    for(int i=0;i<cot;i++)
//    {
//      printf("%d ",prime[i]);
//    }
return 0;
}

③线性筛法(欧拉筛) O(n)

因为在埃氏筛法中,有的数被筛了多次,如6,都被2和3筛过,所以要进行优化,保证每个数只被筛了一次。在欧拉筛法中,我们对每个数都进行枚举,将它与已得到的素数的乘积筛去,而为了保证每个数不重复筛去,如果该数可以被已得到素数整除,那么说明已被筛过。

#include<bits/stdc++.h>
using namespace std;
int prime[50005],//存素数
    flag[50005],//标记数组,是否被筛
    cot;
int main()
{
    int n;
    cin>>n;
    for(int i=2;i<=n;i++)
    {
       if(!flag[i])//如未被筛
       {
        prime[cot++]=i;//储存
       }
       for(int j=0;j<cot&&prime[j]*i<=n;j++)
       {
          flag[prime[j]*i]=1;//将倍数筛去
          if(i%prime[j]==0)//i这个数已被筛,跳出
            break;
       }
    }
//    printf("%d\n",cot);
//    for(int i=0;i<cot;i++)
//    {
//      printf("%d ",prime[i]);
//    }
return 0;
}

其实在埃氏筛中,我们最外层循环是找到素数,然后从再将其所有倍数筛去;
而在欧拉筛中,我们的最外层循环其实就枚举了倍数,然后再在内层循环中与已得到素数相乘,筛去更大的倍数(其实这里就与埃氏筛类似,将素数的倍数筛去),而 if(i%prime[j]==0) break; 这句确保了每个数只被筛一次,以此降低了时间复杂度。

posted @ 2020-03-02 18:42  Pecoz  阅读(169)  评论(0编辑  收藏  举报