素数筛选主要是求1---n中有多少个素数。
主流的有两种,一种是埃式筛法,一种是线性筛法,其中线性筛法是埃式筛法的升级版,但正常情况下埃式筛法就够用了。
然后有种不常用的,就是筛选出区间内的素数,通常这个区间内的值都很大,大到用线性筛也会超时,但区间的范围会相对比较小,这时候就要用区间二次筛选
埃式筛法
总所周知,一个合数总是由几个素数相乘得到的,换句话说,一个素数的倍数肯定就是合数了。
这样,我们只需要现将0---n先全部标记为素数,其中0和1先特判为非素数。然后从2开始,每次遇到一个素数,先记录下来,然后就通过循环来将它的倍数都标记为非素数即可,下次遇到时就可以直接跳过,而没被标记过的就肯定是素数了,因为一个素数的倍数不可能是素数。
下面是相关代码:
const int MAX = 1e7+7;
int prime[MAX];
bool is_prime[MAX];
int cnt;
void Ai_Sieve(int n)
{
cnt = 0;
memset(is_prime,true,sizeof(is_prime));
is_prime[0] = is_prime[1] = false;
for(int i = 2;i <= n;i++)
{
if(is_prime[i])
prime[cnt++] = i;
for(int j = i+i;j <= n;j+=i)
is_prime[j] = false;
}
}
线性筛法:
埃式筛法虽然效率已经很高了,但还是有一些重复的地方,从而使时间复杂度稍微升高了那么一点点,但身为一个强迫症,肯定忍不了!
简单说一下为什么会有重复的地方
一个合数,是由几个素数相乘所得到的,换句话说,几个素数的倍数也都能组成合数
几个素数!!!
如果一个合数的素数都相同,倒也无妨,但如果不相同呢?每个不同的素数都需要标记一次相同的合数
比如6,由2和3组成,当遇到2时,它被标记一次;当遇到3时,它又被标记一次,强迫症忍得了?
那么,如何解决呢?
一个合数a,可以取出最小的素数因子x,其他剩下的因子y,那么,a = x *y。
我们只需要保证这个合数a只被最小的素数因子x标记一次即可!
那么,怎么做呢?
我们同样遍历一边2--n,定义为i
我们有一个prime数组存放之前遇到的素数,将prime[j] *i标记一次,然后j++
重点来了
如果遇到i % prime[j] == 0时,我们就放弃循环,为什么?
因为此时i已经有了prime[j]这个因子了,而prime[j+1]肯定比prime[j]要大,如果不终止,下次就相当于i * prime[j]被大于最小素数因子的素数筛选了!
举个例子
如果i = 6,我们先筛选2*6 = 12,然后由于6 % 2 = 0,所以终止,否则,下次就是筛选3 *6了,也就是让素数3来筛选,但是,此时3*6有比3还小的素数因子,也就是2 *9的2!不符合开始说的一个合数只被它最小的素数筛选!
下面是代码:
const int MAX = 1e7+7;
int prime[MAX];
bool is_prime[MAX];
int cnt;
void Line_Sieve(int n)
{
cnt = 0;
memset(is_prime,true,sizeof(is_prime));
is_prime[0] = is_prime[1] = false;
for(int i = 2;i <= n;i++)
{
if(is_prime[i])
prime[cnt++] = i;
//从这里为止都跟埃氏筛一模一样
for(int j = 0;j < cnt && i*prime[j] <= n;j++)//挑选之前选中的素数,与i相乘,结果标记为合数
{
is_prime[i*prime[j]] = false;
if(i % prime[j] == 0)//如果i中有prime[j]的因子,那么,下一次循环的时候,i*prime[j+1]就会被标记,但是,此时的结果中有比prime[j+1]更小的素数因子
break;
}
}
}
区间二次筛选
有时候,我们需要求区间[L,R]内的素数,但是,L,R都很大,但R-L却相对比较小,这时候就要用到区间二次筛选了。
首先,从 L 到 R ,如果在[2,√R]中没有素数可以整除它,那该数肯定是素数了
所以,我们先筛选出[2,√R]中的素数
此后,用埃式筛选的方法进行第二次筛选,选出[L,R]中的素数
其中最关键的就是直接跳到L了
在埃式筛中,我们通过prime[i] * j = a来筛除掉a
但是,如果我们从1--R来遍历j,肯定就要超时了
所以,需要把j直接跳到所需筛除的区间内,因此j = L/prime[i]
如果有余数,j就需要加1,因为此时的j * prime[i]小于L,在区间范围外
如果j等于1,j就需要加1,否则机会吧prime[i]给直接筛去了
附上POJ 2689的AC代码:
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <vector>
#include <list>
#include <map>
#include <stack>
#include <queue>
#include <set>
using namespace std;
#define ll long long
const int MAX = 1100000;
int prime[MAX];
bool is_prime[1100000];
int cnt;
void Ai_Sieve(int n)
{
cnt = 0;
memset(is_prime,true,sizeof(is_prime));
is_prime[0] = is_prime[1] = false;
for(int i = 2;i <= n;i++)
{
if(is_prime[i])
prime[cnt++] = i;
for(int j = i+i;j <= n;j+=i)
is_prime[j] = false;
}
}
int Sieve2(ll a,ll b)
{
int cnt2 = 0;
Ai_Sieve(48000);
memset(is_prime,true,sizeof(is_prime));
if(a <= 1)
a = 2;
for(int i = 0;i < cnt && (ll)prime[i]*prime[i] <= b;i++)
{
int l = a/prime[i]+ (a%prime[i] != 0);//有余数时,说明求到的值小于需要的值,所以加1
if(l == 1)//l等于1时,j初始化就是个素数,会排除掉这个素数
l++;
for(ll j = (ll)l*prime[i];j <=b;j+=prime[i])
is_prime[j - a] = false;
}
for(int i = a;i <= b;i++)
if(is_prime[i-a])
prime[cnt2++] = i;
return cnt2;
}
int main()
{
ll a,b;
while(cin >> a >> b)
{
int num = Sieve2(a,b);
int max1,max2,min1,min2;
max1 = 0,max2 = 1,min1 = 2e9,min2 = 0;
for(int i = 1;i < num;i++)
{
if(prime[i] - prime[i-1] < min1-min2)
{
min1 = prime[i];
min2 = prime[i-1];
}
if(prime[i] - prime[i-1] > max1-max2)
{
max1 = prime[i];
max2 = prime[i-1];
}
}
if(max1 == 0)
printf("There are no adjacent primes.\n");
else
printf("%d,%d are closest, %d,%d are most distant.\n",min2,min1,max2,max1);
}
//cout << "AC" <<endl;
return 0;
}