线性筛其实NOIp2017之前打过线性筛板子,不过那时候就没理解,临阵磨枪顾不了那么多。

学习数论的时候用到了,又看了一遍。作为一个性子比较急的人找了好几个博客都看不下去,因此花了很长时间才搞懂。

O(sqrt(n))判断n是不是质数就不多赘述了。素数筛之所以叫“筛”是因为它的基本思想是对于1-n批量判素数,由当前已知素数筛去后面的是该素数倍数的合数,以此剩下质数。因此,根据这一基本思想,我们可以初步写出一种粗劣的素数筛算法:

int n;
const int sz=1e6+5;
bool h[sz]={0};//h[i]=0表示i是质数,反之不是
int z[sz],len;//z用于储存已知素数,len表示已知素数的个数
h[1]=1;
for(int i=2;i<=n;i++){
    if(h[i]==0)z[++len]=i;
    for(int j=1;j<=len&&z[j]*i<=n;j++)h[z[j]*i]=1;
}    

然后大家会发现,n一大这个算法的效率就非常呵呵了……

尝试优化这个算法。

分析可以发现,这个算法的效率之所以会低,是因为同一个数有很多质因子,因此会被筛很多遍。如果我们可以让一个数只被筛一次,就可以保证效率优化到O(n)。(这不是废话吗摔)

那么,如何让一个数只被筛一次呢?可以考虑只用这个数最小的质数或者最大的质数去筛这个数。我们的原算法是,对于素数p,晒去p*x(x是大于1的正整数),若p是(p*x)的最小质因数,只要使得p不大于x中的所有质因数即可。原算法可以看作是枚举x,对于每个x从小到大枚举p,我们只要在p增大到恰为x的最小质因数时去枚举下一个x即可。

代码如下:

int n;
const int sz=1e6+5;
bool h[sz]={0};//h[i]=0表示i是质数,反之不是
int z[sz],len;//z用于储存已知素数,len表示已知素数的个数
h[1]=1;
for(int i=2;i<=n;i++){
    if(h[i]==0)z[++len]=i;
    for(int j=1;j<=len&&z[j]*i<=n;j++){
        h[z[j]*i]=1;
        if(i%z[j]==0)break;
    }
}