系统的讨论素数筛法——OI数论

本文介绍了素数的概念,探讨了如何判断一个数是否为素数,以及朴素筛、欧拉筛、埃氏筛和区间筛等高效的素数筛选算法。通过实例和代码展示了不同方法的复杂度和适用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

怎么从浩瀚的数中筛出素数,这是个问题,但是首先我们应该学习他的本质再去了解方法。

什么是素数

(本小段需要小学五年级以上数学知识)

素数:

素数又称质数。一个大于1的自然数,除了1和它自身外,不能被其他自然数整除的数叫做质数;否则称为合数(规定1既不是质数也不是合数)。

所以说素数其实很好判断,正整数n,如果用2到根n之间的所有整数去除,均无法整除,则n为质数。

那么该如何筛选素数呢?

真的真的真的很朴素的朴素筛

不难发现,很多最好想到的数学定理其实都是从定义出发,本想法也是一样的,所以说我们只需要在[2,n)这样一个区间里去遍历就可以了,如果是的话就sum ++,看代码:

//朴素筛
bool judge(ll x)
{   
    if(x==2) 
    return true;
    if(x<2||x%2==0)
       return false;   
    for(int i=3;i<=sqrt(x+1);i+=2) 
        {
            if(x%i==0)
            return false;
        }
    return true;
}

复杂度O(n√n)

欧拉筛

这是一种很好的线性筛法,欧拉筛法只筛除一次。

//欧拉筛
int prime[maxn];
int factor[maxn];
int Prime(int n)
{   
    int p=0;
    for(int i=2;i<=n;i++)
    {
        if(!factor[i])
        {
            prime[p++]=i;
            factor[i]=i;            
        }
        for(int j=0;j<p&&prime[j]*i<=n;j++)
        {
            factor[prime[j]*i]=prime[j];
            if(!(i%prime[j])) 
                break;      
        }   
    }
    return p;   
}

复杂度O(n)

埃氏筛 

利用当前已经找到的素数,从后面的数中筛去当前素数的倍数,由预备知识一可知,当前素数已经是筛去数的质因子,如此下去能筛除所有之后的合数

//埃氏筛
int prime[maxn];
bool is_prime[maxn];
int Prime(int n)
{   
    memset(is_prime,true,sizeof(is_prime));
    int p=0;
    is_prime[0]=is_prime[1]=false;
    for(int i=2;i<=n;i++)
    {
        if(is_prime[i])
        {   
            prime[p++]=i;
            for(int j=2*i;j<=n;j+=i)
                is_prime[j]=false;              
        }

    }
    return p;   
}

复杂度O(nloglogn)

区间筛(转载Prudento 的博文,表示感谢)

eg: 给定整数 a和b,请问区间[a,b)内有多少个素数

a<b<=10^12

b-a<=10^6

观察上面的例题,区间两边都是不定的。上面介绍的埃氏筛法解决了快速筛出2~n区间的所有素数。那么当左右区间都是不定时,如何才能高效筛选出素数?那么这里便要用到区间筛法。

区间筛法:
已知,对于任意合数b,其最小质因子<=根号b。如果根号b以内的素数表,便可以把埃氏筛法运用到区间[a,b)上了。也就是说,先分别做好[2,根号b)上的表和[a,b)上的表,然后从第一个表里筛得素数得同时,也将其倍数从第二个表中划去,因为b是右区间临界点,sqrt(b)显然是最大的质因子的上界,所以a~b这个区间里边的合数只可能被小于等于根下b的素数筛掉,最后剩下得便是所求区间内得素数。                        
原文链接:https://blog.csdn.net/Prudento/article/details/123956422

 

#include<bits/stdc++.h>
using namespace std;
#define LL long long
 
#define MAX_L 1000007
#define MAX_SORT_B 1000007
 
int  is_prime[MAX_L];
int is_prime_small[MAX_SORT_B];
 
//对区间[a,b)内的整数执行筛法。isprime[i - a]=1 <=> i是素数
 
void segment_sieve(LL a,LL b)
{
    for(int i=2; (LL)i*i < b; i++)is_prime_small[i]=1;//初始化 [2,sqrt(b)))
    
    for(int i=0; i<b-a; i++)is_prime[i]=1;//初始化 [a,b) 但是a和b的范围很大,通过b-a压缩区间 
    
    for(int i=2; (LL)i * i<b; i++)筛[2,sqrt(b))
    {
        if(is_prime_small[i])
        {
            for(int j=2*i; (LL)j * j < b; j += i)//如此写比sqrt更快且防溢出 
            {
                is_prime_small[j]=false;
            }
            //同时筛 [a,b)
            //筛至少从i的2倍开始,2LL即把2转为LL型 
            //假如区间为[10,20),i=2时,(a+i-1)/i,即(10+2-1)/2=5 那么应该从2的五倍开始筛,此后同理。 
            for(LL j=max(2LL, (a+i-1)/i)*i ; j<b; j+=i) //(a+i-1)/i为[a,b)区间内的第一个数至少为i的多少倍.
            {
                is_prime[j - a] =false;//筛[a,b)
                //因为初始化时通过b-a压缩区间.把a~b 压缩成0~b-a-1.所以is_prime[i]则表示数a+i,同理,j-a也是压缩区间。
				//如a+1 是is_prime[1],下标为a+1-a。同理,a+j的下标便是j-a。 
            }
        }
    }
}
 
int main()
{
    long long a,b;
    while(~scanf("%lld %lld",&a,&b))
    {
        segment_sieve(a,b);
        int cnt=0;
        for(int j=0; j<b-a; j++)
        {
            if(is_prime[j])cnt++;
        }
        if(a==1)cnt--;
        printf("%d\n",cnt);
    }
    return 0;
}

廿四筛

自我发明

我似乎发现了一种很新的素数筛法-CSDN博客

总结

数论板子持续更新

真好玩ye~~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值