素数筛
我们在判断一个数是否是素数时,通常会写一个函数来判断
bool isprime(int x){
int m = sqrt(x);
for(int i = 2; i <= m; i++){
if(x % i == 0) return false;
}
return true;
}
当我要求几个数是否是素数时,这样的做法确实比较方便。
但是,当我们要求一定范围内的数,有多少是素数,一个一个判断就显得效率有些低下。
这个时候就要用到素数筛。0.
Eratosthenes素数筛(又称埃氏筛法)的基本原理:
对于不超过n的每个正整数p,删除2p,3p,4p…,当处理完所有的数据之后,没有删除的数据就是素数。
代码层面,我们可以初始化一个vis数组为0,表示所有的数都为素数,当这个数被删除之后,就将对应的vis[i]置为1,表示这个数不是素数。 换言之,vis数组为0的元素是素数。
void Prime(int n){
memset(vis,0,sizeof(vis));
for(int i = 2; i <= n; i++)
for(int j = 2*i; j <= n; j += i) vis[j] = 1;
}
虽然这样做的时间复杂度已经很高了,但是还可以继续优化。
首先是第一重循环,我们在求取数组中的素数时,可以将n开方,到sqrt(n)时,所有的非素数都已经筛选完毕。
因为素数的倍数不是素数,所以我们可以将p限定为素数
最后j循环也不必从2i开始,因为2i已经在2的时候被筛掉了,3,4,5同理,所以将其改为i的平方
所以最后的模板为:
void Prime(int n){
int m = sqrt(n+0.5);
memset(vis,0,sizeof(vis));
for(int i = 2; i <= m; i++)
if(!vis[i]) for(int j = i*i; j <= n; j += i) vis[j] = 1;
}
到这里,Eratosthenes素数筛的效率进一步被优化,但是我们可以看到,由于一个数可能有很多因数,所以也就会被筛到很多次,不经意间做了很多无用功。
所以这里引出最强素数筛:
线性筛
基本思路:在筛法的基础上 我们让每一个合数只能被他自己最小素因子筛到一次
在筛i*prime[j]的时候,他的最小素因子就是prime[j],也就是在第一次满足于i % prime[j]为0的时候,将这个数筛掉,就可以保证所有的合数只筛一次。
也可以这样说,因为素数数组的值是递增的,所以在i整除prime[j]时,prime[j+1]一定会被后面的某个数筛掉
举个例子:当i = 5,prime[j] = 5,筛掉25,prime[j+1] = 7时的35会被在i = 7,prime[j] = 5的时候被筛掉
线性筛的模板如下:
int Prime(int n){
int cnt = 0;
for(int i = 2; i < n; i++){
if(!vis[i]) prime[cnt++] = i;
for(int j = 0; j < cnt && i * prime[j] < n; j++){
vis[i*prime[j]] = 1;
if(i % prime[j] == 0) break;
}
}
return cnt;
}