前言
如果只对一个整数进行素性测试,通常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;
}
参考白书。