题解 P3383 【【模板】线性筛素数】

· · 题解

本题更新后,数据经过了加强,必须使用线性复杂度O(N)的筛法。

#include<cstdio>
#include<iostream>
using namespace std;
int main()
{
    int n, q, check[100000001]={0};
    int prime[1000001],cnt=0;
    //根据n<=10^8,q<=10^6开对应的数组大小,防止MLE
    //初始将check数组全部标记为0,标0的是素数,标1的不是素数
    scanf("%d%d",&n,&q);
    check[1]=1;//1不是素数
    for(int i=2;i<=n;i++)
    {
        if(!check[i])prime[cnt++]=i;//若当前数i没有被之前的所有数筛掉,表明i是素数,将i添加进素数表prime
        for(int j=0;j<cnt&&i*prime[j]<100000001;j++)//注意i*prime[j]不要超过n的上限(10^8)
        {
            check[i*prime[j]]=1;//将当前素数prime[j]的i倍标记为合数
            if(i%prime[j]==0)break;//关键步骤:保证每个合数只被筛一次
        }
    }
    for(int i=1;i<=q;i++)
    {
        scanf("%d",&n);
        printf("%d\n",prime[n-1]);//由于素数表从0开始存,所以输出时下标应减1
    }
    return 0;
}

算法原理

素数筛法的原理:把不是素数的筛掉,剩下的就都是素数。

欧拉线性筛的关键在于:每个合数只被它最大的非自身的因数筛掉

例如:

$45$不会被$9$筛掉,而是被$15$筛掉。 |当前用于筛素数的数i | 被i筛掉的数 |被i筛掉的数 |被i筛掉的数 |被i筛掉的数 | | :----------: | :----------: | :----------: | :----------: | :----------: | |2 |2 * 2=4 | | | | |3 |3 * 2=6 |3 * 3=9 | | | |4 |4 * 2=8 | | | | |5 |5 * 2=10 |5 * 3=15 |5 * 5=25 | | |6 |6 * 2=12 | | | | |7 |7 * 2=14 |7 * 3=21 |7 * 5=35 |7 * 7=49 | |8 |8 * 2=16 | | | | |9 |9 * 2=18 |9 * 3=27 | | | |10 |10 * 2=20 | | | | ------------ ### 模拟程序运行过程 我们以 **$12$不被$4$筛掉而被$6$筛掉** 为例: 当 $i$ 循环执行到 $i=4$ 时: 素数表$prime$中已有两个素数:$2$和$3$。 此时在$check$数组中已经被标记为$1$的数(合数)是:$4$、$6$、$9$。 因为$i=4$已经被标记为$1$,所以我们不将$4$添加进素数表$prime$。 此时执行 $j$ 循环: 因为素数表$prime$中有$2$和$3$,所以预计将要被筛掉的数是$4 \times 2 = 8$ 和 $4 \times 3 = 12$。 当$4 \times 2 = 8$被筛掉以后,经过 $i \bmod prime[j]=0$ 判断可以知道$4\bmod2=0$, 此时应结束 $j$ 循环,不再筛 $4 \times 3 = 12$。 ------------ ### 关键步骤解释 我们都知道$12$的最大因数(非自身,下同)是$6$,因此$12$应该被$6$筛掉,那么上述判断的原理是什么呢? **因为当出现 $i \bmod prime[j] = 0$ 的情况时,意味着$prime[j]$ 是 $i$ 的一个因数($2$是$4$的一个因数)。** **既然$4$是$12$的因数,$2$又是$4$的因数,所以$2$也是$12$的因数。** **由此可以知道,$4$不是$12$的最大因数,$12$的最大因数会在后面出现,即$2$所对应的$6$,$12$将会在$i=6$时被筛掉。** 同理可以判断:$9$是$45$的因数,$3$又是$9$的因数,所以$3$也是$45$的因数,$9$不是$45$的最大因数,$45$的最大因数将会在后面出现,即$3$所对应的$15$,$45$将会在$i=15$时被筛掉。 ------------ ### 注意 此方法空间开销较大,注意内存溢出。 数据量大,建议使用 $scanf$ , $printf$ 函数进行读写操作。 ------------ 第一次写题解,不足之处请指出。