在许多题目中,质数筛都并不是一道题拿分的决定性因素,但用了好的筛法往往可以是时间复杂度锦上添花,争取高分。
原始筛法:
思路:
这种筛法也不能说是废的,起码它好理解,容易操作,适用于判断单个数字。
整体很好理解,从 2 2 2 一直到 n \sqrt{n} n,若能除得尽其中任意一个数字,就不是质数。
代码:
bool prime(int n){
for(int i=2;i<=sqrt(n);i++){
if(n%i==0)return false;
}
return true;
}
埃氏筛法:
思路:
埃氏筛法利用了一个素数的倍数一定不是素数、任何一个合数可以表示成一个素数和另一个数乘积的性质。
对于一定的范围,先假定它们都是质数,然后从 2 2 2 开始,先判断如果是素数,把它在范围内的倍数乘积都筛去,以此类推循环至 s q r t ( n ) sqrt(n) sqrt(n) 即可。
代码:
void isprime_B(int b){
memset(is_prime,true,sizeof(is_prime));//先假设都为素数
for(int i = 2;i <= sqrt(b);i++){
if(is_prime[i]){
prime[q++] = i;
for(int j = i*2;j <= b;j += i)is_prime[j] = false;
}
}
}
欧拉筛法:
思路:
看懂上面埃氏筛法的 DALAO 可以跳过此基础部分,直接看进阶部分。 \color{white}\colorbox{red}{看懂上面埃氏筛法的 DALAO 可以跳过此基础部分,直接看进阶部分。} 看懂上面埃氏筛法的 DALAO 可以跳过此基础部分,直接看进阶部分。
基础(埃氏筛法):
欧拉筛的主题思想就是每搜到一个质数,就把它的倍数标记为合数,并把它加入队列。
那么怎么判断搜到的是不是质数呢?总不可能暴力判断吧,如果用暴力,那么就喜获 TLE
一枚。
其实我们每搜到一个数,如果是质数,那么它的倍数(肯定是合数)就标记一下。
那么我们就能知道:对于每个我们搜到的数,如果它被打过标记,那么它就不是质数,否则它就是质数。
进阶:
我们用了一行代码来减少时间复杂度:
if(i%prime[j]==0)break;
怎么实现的呢?
对于一个数,它可能有不止一个质因子,那么根据上面那个埃氏筛法,它会把每一个质数的倍数都标记一遍,也就是说,一个合数,它可能被标记了好几次。
例:对于 14 14 14,它会在 2 2 2 时标记一次, 7 7 7 时标记一次。共标记了两次。
有了这个,就可以避免多余的计算了。
最后浅浅算一下复杂度:时间、空间复杂度为 O ( n ) O(n) O(n)。 N i c e Nice Nice,可以过数据,这种 O ( n ) O(n) O(n) 的算法对我这样的蒟蒻实在是太友好了。 Q W Q QWQ QWQ
代码:
void euler(){ //isprime[i]代表此数是否打了标记,prime[i]存储已搜到的质数
memset(isprime,true,sizeof(isprime));
isprime[1]=false;
for(int i=2;i<=n;i++){
if(isprime[i])prime[++cnt]=i;
for(int j=1;j<=cnt&&i*prime[j]<=n;j++){
isprime[i*prime[j]]=false;
if(i%prime[j]==0)break;
// 最神奇的一句话,如果i整除prime[j],退出循环
// 这样可以保证线性的时间复杂度
}
}
}