素数筛线性筛详细详解(个人总结思路超长版)

一、埃氏筛

        由于传统的sqrt写法比较简单,直接进行相除看是否能整除即可,这里不想过多的介绍此种方法。大多数比赛中这种写法也只能用于判断少量数据或无需大表即可通过的题目。这里从此种埃氏筛开始介绍。

        此种筛法原理即为预打表,用一个isprim[]数组去记忆每一个数字是否为质数。将小于等于数据范围的数字依次进行遍历,然后进行翻倍,将翻倍后的结果标记为合数,其余未被标记的即为质数。代码如下:

#include 
using namespace std;
const int maxn=10000;
int isprim[maxn+5];
int main(){
    memset(isprim,0,sizeof isprim);//重置标记所有为都质数
    isprim[1]=1;//1是合数
    for(int i=2;i<=maxn;i++){
        for(int j=2;i*j<=maxn;j++){
            isprim[i*j]=1;//翻倍的结果是合数,其余被开头重置标记为质数
        }
    }
    for(int i=1;i<=15;i++){
        cout<

        由于每次都是翻倍到maxn,故复杂度为n+n/2+n/3+...+1,由数学公式推论(嗯)可得,复杂度为n*logn。此种做法因为编写速度和思考速度都比较快,如果题目没有特别强制的卡N复杂度要求可以直接利用这个进行解答,毕竟早做出来排名就能靠前一点。

二、线性筛

        如果题目卡复杂度卡的特别死,知道N就需要用到此筛,在上述筛法进行一些小优化,上述筛法在将30进行标记时,会在2*15,3*10,5*6等都进行标记,浪费了速度,如果有种让所有数字都只标记一次的做法即可达到N的复杂度。

        于是我们考虑每个合数都可以拆解为若干质数,所以理论上将所有质数翻倍即可找到所有合数,但是此想法依然有一定缺陷,在计算35=5*7这类数时,还是在7时算到7*5,优化但没完全优化,于是我们思考让每个合数都让其最小的质因数进行翻倍标记,即可达到效果。

        在理解上述思路后,我们进行代码如何编写的思考,如果按照上述逻辑进行翻倍,我们发现在外层循环无法直接获取所有质数,即使通过边算边加的方式让质数循环硬放到外层逻也会十分难以思考,于是我们逆转思维,将倍数放在外层,内层循环我们已经得到的质数,此时质数问题解决,只剩如何让每个合数都被最小质数抛去。

        我们在每次进行乘积后,进行一次i%prim[j]==0的判断,如果成立,则证明此个数字一定能被其更小的数字相乘得到,例如在外层i=10,prim[j]=3,这个情况不会出现,因为在prim[j]=2时i就已经因为除尽退出循环,所以30的重任是被i=15,prim[j]=2是筛掉的,因为10是2的倍数,所以一切能被10想成得到的合数一定也会被2相乘得到,所以在10碰到2的时候会直接终止循环退出,简化了很多无用的筛选次数,因为每个数只被筛一次,所以复杂度为N。代码如下

#include 
using namespace std;
const int maxn=10000;
int isprim[maxn+5];
int prim[maxn+6];
int main(){
    memset(isprim,0,sizeof isprim);//重置标记所有为都质数
    isprim[1]=1;//1是合数
    int cnt=0;//当前找到的质数个数
    for(int i=2;i<=maxn;i++){
        if(isprim[i]==0){
            prim[++cnt]=i;//将算好的质数添加到prim数组
        }
        for(int j=1;i*prim[j]<=maxn&&j<=cnt;j++){
            isprim[i*prim[j]]=1;//翻倍的结果是合数,其余被开头重置标记为质数
            if(i%prim[j]==0){
                break;//如果此时能除尽,则证明这个数可以被更小的除掉
            }
        }
    }
    for(int i=1;i<=15;i++){
        cout<

 萌新第一次发帖,欢迎指正错误

你可能感兴趣的:(ACM,算法,剪枝,c++,性能优化)