暴力筛选
也被就叫做试除法,对于每一个整数N,你都可以进行一次从2到根号N的筛选,如果这个数不能被从2到根号N的任意一个数整除,就可以确定这个数是素数.
int prime[maxn],num;
void Simple_Screening(int n)
{
for(int i=2;i<=n;i++){
int flag=0;
for(int j=2;j*j<=i;j++){
if(!(i%j)) {flag=1;break;}
}
if(!flag) prime[num++]=i;
}
}
对于这种最朴素的方法所需的时间复杂度也是最高的,对于每一个N你需要O(n2)的时间复杂度。
埃氏筛(Eratosthenes)
朴素的筛选方法在竞赛中肯定是行不通的。那么我们这样考虑一个问题:对于每个质数,他的任意不包括一的整数倍必然是一个合数。我们可以对从2到N的整数这样筛选:每当找到一个质数时,便帮他的任意倍标记为合数。如果这个数被标记为合数,那便跳过对这个数的操作。
int prime[maxn],num,visit[maxn];
void Eratosthenes(int n)
{
for(int i=2;i<=n;i++){
if(!visit[i]){
for(int j=2;j*i<=n;j++){
visit[i*j]=1;
}
prime[num++]=i;
}
}
}
那么,这种方法的时间复杂度是在2到N中所有的质数的N/P的加和。化简后也就是O(N log log N),这个复杂度接近线性,比赛时大多数时候使用这种方法。
线性筛(欧拉筛)
埃氏筛是非常接近线性的筛法,但是当遇到n=10000000的情况时还是会被卡掉。我们对埃氏筛分析会发现,这个数i有多少个质因子就会被筛掉多少次,对此我们可以这样处理
int prime[maxn],num,visit[maxn];
void Euler(int n)
{
for(int i=2;i<=n;i++){
if(!visit[i]) prime[num++]=i;
for(int j=0;j<num,prime[j]*i<=n;j++){
visit[i*prime[j]]=1;
if(!(i%prime[j])) break;
}
}
}
注意这里面的这个if语句
if(!(i%prime[j])) break;
这个语句就表明的每个合数只被他的最小质因子标记一次。
没有这个语句,会继续标记i * prime[j+1],但是i里有个质因子是prime[j],也就意味这这个被标记的数同时也会被prime[j] * (i/prime[j]*prime[j+1])标记一次。这样就会发成同一个合数被标记多次的情况。也就是说这个if语句使得同一个合数被标记且只被标记一次。当然这个算法的时间复杂度是O(n)。