写在前面:Li Zhuohan的BLOG对本文的欧拉筛部分有极大启发。
埃氏筛(Aye Sieve):
原理:
-
遍历每个素数,将它的倍数筛掉。
-
为什么从 i*i 开始?例如 i = 7,如果从任意 i * a(a < i) 开始,若 a 是质数,则 a 可以筛掉它;若 a 是合数,则必有比 a 还小的质数可以筛掉它。
如此,我们发现埃氏筛法保证了每次新的循环不再重复筛较小的一部分数字,但是由于 j += i,后面的数还是有被重复筛选的现象,这启发了我们对欧拉筛的理解。
for(int j = i*i ; j <= maxn ; j+=i)
代码:
#include <iostream>
#include <cstring>
using namespace std;
const int maxn = 1000005;
const int submaxn = 1000;
bool un_prime[maxn];
void prime(){
memset(un_prime , false , sizeof(un_prime));
un_prime[0] = un_prime[1] = true;
for(int i = 2 ; i <= submaxn ; i++)
if(!un_prime[i])
for(int j = i*i ; j <= maxn ; j += i)
un_prime[j] = true;
return ;
}
int main(){
prime();
return 0;
}
欧拉筛(Euler Sieve):
原理:
- 埃氏筛是用(外层循环)i 除去内层循环 j;而欧拉筛是用(外层循环)j 来筛掉内层循环 primelist[i]。
- 必须要明白:欧拉筛法的优越性在于:所有合数都由它的最小质因子筛出,并且显然该最小质因子(如 2、3 )会被使用很多次(不要忘了这点)。
- 接下来的理解包含一些个人想法,并未严格证明。
- 首先认为所有大于等于二的整数分为两类:只有质因子,有至少一个合因子。
- 其次,认为是 “使用” primelist[i] “借助” j 来筛数。
对于每个 j:
若它是质数,则它可以筛去所有可以筛的数,且那些数均没有合因子。为什么?例如用 7 去筛 35,35的最小质因数是 5,但因为:任何一个素数 n = A * B,A和B必须不严格地分布在 sqrt(n) 的两侧(这也是为什么求素数的朴素解法只需要遍历到 sqrt(n) 的原因)。而在使用 5 的时候只能使用比它小的质因数,所以 35 必定还没有被筛掉。
若它是合数,则必须在筛完它的最小质因子 * 它之后就停止对它的使用。因为它想筛选的下一个数的最小质因子必然是它的最小质因子,那个数不可以由下一个质数(即该最小质因子的下一个质数)筛选。例如 12 不可以被 3 借助 4 筛出,必须由 2 借助 6 筛出。
代码:
#include <iostream>
#include <cstring>
using namespace std;
const int maxn = 1000005;
bool un_prime[maxn];
int primelist[maxn];int cnt=1;
int N = 1000000;
void prime(){
memset(un_prime , false , sizeof(un_prime));
memset(primelist , false , sizeof(primelist));
for(int j = 2 ; j <= maxn ; j++){
if(!un_prime[j])//若是素数,加入素数表,本循环即刻可用
primelist[cnt++] = j;
for(int i = 1 ; i < cnt ; i++){
int cur = j * primelist[i];
if(cur > N)//不需要判断到那么多
break;
un_prime[cur] = true;//先筛数,因为如果最小质因数是 2,就必须在这次筛掉
if(j % primelist[i] == 0)
break;//如果 j 是合数,且最小质因子为 primelist[i],就可以退出了
}
}
return ;
}
int main(){
prime();
return 0;
}