关于素数的埃氏筛法/区间筛法 C++(代码实现和详解)

前言

如果只对一个整数进行素性测试,通常O(n^1/2)的算法便足够了。而程序竞赛设计的主要是埃氏筛法等更高效的算法。如果要对许多整数进行素性测试,则需要利用更加高效的算法,此次以例题为媒介,介绍埃氏筛法和区间筛法。

一:埃氏筛法

eg:题目描述  

给定整数n,请问n以内有多少个素数?

n<=10^6

看到这,你可能会轻蔑的笑了,就这题,自己分分钟秒杀。但是请别急,下面介绍的埃氏算法,能让你更快解决。

埃氏算法氏和辗转相除法一样古老的算法,其大致思路如下

首先,将2~n范围内的所有数都写下来,存入一张线性表里(用一维数组实现)。其中最小的数字2是素数。将表中2的倍数都划去,当然也包括其本身。之后表中剩余的最小数字是3。它显然也是素数。那么继续将所有3的倍数划去。同理,依次类推,每次表中剩下的最小数字m都是素数,然后将表中所有的m的倍数划去。如此反复操作,便能筛出n以内的所有素数。

埃氏筛法代码实现

Code

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+10;
int prime[maxn];//存素数 
int is_prime[maxn];//is_prime[i]=1即表示i为素数 
int n; 
int main(){
	
	scanf("%d",&n);
	int p=0;//表示筛出来的素数的位数 
	for(int i=2;i<=n;i++)is_prime[i]=1;//从2开始初始化,0和1显然非素数非合数 
	
	for(int i=2;i<=n;i++){//开始筛 
		if(is_prime[i]){//依次遍历,每次表中is_prime[i]值仍然为1的最小的数便是一定是素数 
			prime[p++]=i;//筛出素数 
			for(int j=2*i;j<=n;j+=i)is_prime[j]=0;//倍数都划去 
		}
	}
	for(int i=0;i<p;i++)//输出素数 
	printf("%d ",prime[i]);
return 0;
}

相信你看完便懂。

二:区间筛法

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的素数筛掉,最后剩下得便是所求区间内得素数。

区间筛法代码实现:


Code

#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;
}

参考白书。

  • 5
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Prudento

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值