1.埃氏筛
思想:任意合数都可以表示成几个素数的乘积,那么我们可以每找到一个素数,就将他的倍数都标记(代表这个数是合数)。2是最小的素数,所以我们从2开始标记。时间复杂度为:O(n*logn)
代码如下:
const int maxn=1e5+10;
bool flag[maxn];
int prime[maxn];
int pri_cnt=0;
void get_prime(int n){
for(int i=2;i<=n;i++){
if(!flag[i]){
prime[++pri_cnt]=i;
for(int j=i*i;j<=n;j+=i){
flag[j]=1;
}
}
}
}
对于上面j=i*i
可能会有人有疑问,按照上面的思路,初始值j
不应该等于2*i
吗,因为区间i*(2~ i-1)
在 2~i-1
时都已经被筛去,所以从i * i
开始.
2.欧拉筛
思想:由埃氏筛我们已经知道筛法的核心就是标记,但是我们可以发现:即便我们做了一点优化,随着数据量的增长,一些拥有多个素因子的合数会被重复标记。我们可以想到用唯一素数来标记合数,这样每个合数都只会被标记一次,因此时间复杂度降到O(n);
代码如下:
const int maxn=1e5+10;
int v[maxn],prime[maxn];//数组v记录每个数的最小质因子
int pri_cnt=0;//pri_cnt记录质数的个数
void prime_table(int n){
for(int i=2;i<=n;i++){
if(v[i]==0){//i是质数
v[i]=i;
prime[++pri_cnt]=i;
}
for(int j=1;j<=pri_cnt;j++){
//如果(i*prime[j])有比prime[j]更小的因子,或者超出n的范围,停止循环
if(prime[j]>v[i] || prime[j]>(n/i)) break;
//prime[j]是合数i*prime[j]的最小因子
v[i*prime[j]]=prime[j];
}
}
}
prime[j]>v[i]
表示当前的素数prime[j]
比累乘因子i
的最小素因子大,这就代表合数i*prime[j]
应该被i
的最小素因子v[i]
筛掉,而不是被当前的素数prime[j]
筛掉,举个例子:12=4x3;当i=4 prime[j]=3
时,12本应该被2筛掉,也就是被4的最小素因子2(v[4]=2
)筛掉,轮不到3来筛,所以跳出循环。
prime[j]>(n/i)
是prime[j]*i>n
的意思,用除法不用乘法是为了防止溢出(当数据量很大的时候,会爆int)