几种素数筛法
大吉!
![](https://i-blog.csdnimg.cn/blog_migrate/4840dea441da7a5e09439982d17b128e.jpeg)
朴素判断
通过素数的定义判断,只能被1和本身整除的数是素数。一般只有求单个数是否为素数时才会采用这种方法,若求的是范围的,一般会TLE。判断方法就是看数n能否被 2 n − 1 2~n-1 2 n−1内的数整除,遍历到 n \sqrt{n} n即可。[例如10=2×5=5×2是被重复计算的,无必要,可优化]
bool isPrime1(int x){
for(int i=2;i*i<=x;i++){
if(x%i==0) return false;
}
return true;
}
若要求多个素数,时间复杂度达到 O ( n n ) O(n\sqrt{n}) O(nn)
埃氏筛法(Eratosthenes)
当要判断的素数是一个范围时,朴素的判断显然不可行。这时候可用到埃氏筛法,由于素数的倍数一定不是素数,因此可以利用当前已经找到的素数,到后面的数中筛去倍数。
附上一个好理解的图(图源网络)
![](https://i-blog.csdnimg.cn/blog_migrate/b94614e5100f7fa67f2a7308b3b03818.gif)
bool is_prime[maxn];//用来判断是否为素数
int prime[maxn];//用来存放素数
int cnt;//记录素数的个数
void isPrime2(int n){
memset(is_prime[maxn],1,sizeof(is_prime));
for(int i=2;i<n;i++){
if(is_prime[i]){
prime[cnt++]=i;
for(int j=2*i;j<n;j+=i){
is_prime[j]=false;
}
}
}
}
此时的时间复杂度为 O ( n l o g n l o g n ) O(nlognlogn) O(nlognlogn)
欧拉筛(Euler)
由上面的图可以看到某些数其实是被筛了很多遍了,比如6=2×3=3×2,筛了两遍。而欧拉筛就是在埃氏筛的基础上,让每个合数只被它最小的质因子筛选,达到不重复筛除的目的,这样的筛时间复杂度就是线性的了。
欧拉筛时我们用
p
r
i
m
e
[
]
prime[]
prime[]数组来存放已找到的素数,对于每个
i
i
i,我们筛除
i
i
i×
p
r
i
m
e
[
j
]
prime[j]
prime[j],那怎么确保一个合数只被筛除一次呢,我们注意到
如果
i
i
i%
p
r
i
m
e
[
j
]
prime[j]
prime[j] =0 →
i
i
i=
k
k
k×
p
r
i
m
e
[
j
]
prime[j]
prime[j] →
i
i
i×
p
r
i
m
e
[
j
+
1
]
prime[j+1]
prime[j+1]=
k
k
k×
p
r
i
m
e
[
j
]
prime[j]
prime[j]×
p
r
i
m
e
[
j
+
1
]
prime[j+1]
prime[j+1]
令
m
m
m=
k
k
k×
p
r
i
m
e
[
j
+
1
]
prime[j+1]
prime[j+1],以上式子可化成
m
×
p
r
i
m
e
[
j
]
m×prime[j]
m×prime[j]
显而易见了,当
i
i
i%
p
r
i
m
e
[
j
]
=
0
prime[j]=0
prime[j]=0以后,
i
i
i×
p
r
i
m
e
[
j
+
1
]
prime[j+1]
prime[j+1]会被别的数筛除,因此此时break可确保仅被筛除一次。
bool is_prime[maxn];//用来判断是否为素数
int prime[maxn];//用来存放素数
int cnt;//记录素数的个数
void isPrime3(int n){
memset(is_prime,1,sizeof(is_prime));
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++){
is_prime[i*prime[j]]=false;
if(i%prime[j]==0) break; //关键部分
}
}
}
时间复杂度 O ( n ) O(n) O(n)
大数区间筛
对于一个很大的区间,例如
a
<
b
<
1
0
18
a<b<10^{18}
a<b<1018,
b
−
a
<
1
0
6
b-a<10^{6}
b−a<106
数组无法开到那么大,但是区间大小小于
1
0
6
10^6
106这类问题就用到区间大数筛,是基于埃氏筛或欧拉筛,然后将数组进行偏移。
步骤是使用埃氏筛或欧拉筛求出
[
2
,
b
]
[2,\sqrt{b}]
[2,b],对于每个素数p,把
[
a
,
b
]
[a,b]
[a,b]中能被p整除的数标记,也就是标记
i
×
p
i×p
i×p,其中
⌈
a
p
⌉
\lceil\frac{a}{p}\rceil
⌈pa⌉<
i
i
i<
⌈
b
p
⌉
\lceil\frac{b}{p}\rceil
⌈pb⌉
bool is_prime1[maxn];//用来打小表
bool is_prime2[maxn]//用来判断大区间是否为素数
void isPrime4(LL a,LL b){
LL i,j,p=0;
for(i=0;i*i<b;i++) is_prime1[i]=1;
for(i=0;i<b-a;i++) is_prime2[i]=1;
for(i=2;i*i<b;i++){
if(is_prime1[i]){
for(j=2*i;j*j<b;j+=i) is_prime1[j]=false;
for(j=max((LL)2,(a+i-1)/i*i);j<b;j+=i){
is_prime2[j-a]=false;
}
}
}
}