题主在刷题过程中碰到一个筛素数的问题:看题解的时候发现了这两种筛法,下面为大家一一讲解一下。
最传统的查找一个数是否为素数:
int flag[1000];
int n;
cin>>n;
for(int i=2;i<n;i++)
{
if(n%i==0) flag[i]++;
}
这个是查找n是否为素数,如果要查找2~n的素数有哪些,则要两重循环复杂程度为o(n^2),效率非常慢。
我们观察所有的非素数,发现它们的因子数量都是偶数个(不算1和本身),并且可以分为m对。
比如说 6——(2,3) 12——(2,6)(3,4)
它的两个因子如果乘积是它本身,那么这两个因子就是一对,我们可以表示成( i ,n / i )。
所以我们只要枚举到 ( i<= n/i )即可,也就是枚举所有因子前一半最小的数。
int flag[100000];
int main()
{
int n;
cin>>n;
for(int i=2;i<=n/i;i++)
{
if( n%i==0)
{
flag[i]++;
flag[n/i]++;
}
}
}
这样可以缩短判断n是否为素数的循环次数。
下面引出一个结论:
假设我们要求出100以内的所有素数,那么我们只需要求出 sqrt(100)以内的所有素数,也就是10以内的所有素数, 2,3,5,7。 100以内的非素数就都是与这四个素数的倍数有关的。
数学归纳:
如果我们要求出n以内的所有素数,那么我们只需要求出sqrt(n)以内的所有素数。
这样,就可以丝滑地引入埃氏筛:
int flag[1000000];//标记这个数是否为质数,如果不是就标记为1
int main()
{
int n;
cin >> n;
for (int i = 2; i * i <= n; i++)
{
if (flag[i] == 0) //这个数字没有被标记,说明是质数
for (int j = 2*i; j <= n; j += i) //从这个质数的两倍开始(非素数)
flag[j] = 1; //( 标记这个非素数)
}
}
埃氏筛的时间复杂度是O( ).
但是埃氏筛也不是最完美的,我们可以观察到,同一个数,可能是多个素数的倍数,所以它会被重复筛出,比如6,会被2筛一遍,又会被3筛一遍。
那还有没有更快的呢?
我们就要引出今天的主角,大名鼎鼎的欧拉筛:
欧拉筛的核心思路就是:让每一个合数只被它的最小质因数筛掉,这样就不会出现重复筛的情况。
在讲欧拉筛之前,先说两个小知识:
第一个:
3 是 9的最小质因数,3也是9*3的最小质因数,同理,3也是9*9 或 27*3 的最小质因数.
那么我们由此推理:任何一个合数,乘上任意的倍数,它的最小质因数都不变.
换种说法,任意一个合数,如果按照质数表从小到大依次除以质数,当第一次除到一个质数,余数是0,那么这个质数就是它的最小质因数。
第二个:
所有的合数,都可以分解成:质数*质数,质数*合数
那么现在我们就可以就上代码,然后一点一点分析。
int prime[10000000];//质数表,用来贮存质数
int flag[10000000];//标记这个数是否为质数
int cnt=1;
void get_prime(int n)
{
for(int i = 2; i <= n; i++) //两个作用:1,做倍数,2,贮存质数
{
if( flag[i]==0 ) prime[cnt++] = i; //如果i没被标记,说明是质数。
//我们依次把这样的质数存入质数表中
//下标代表2~n中的第几个质数
for(int j = 1; j<cnt and prime[j]*i <= n; j++) //j代表质数表的下标
//j要小于现有的存入质数表的质数个数
//倍数*prime[j]==合数,一定是要小于n的
{
flag[prime[j]*i] = 1; //把合数进行标记,
if(i % prime[j] == 0) break;
//从小到大依次判断质数表中的质数是否为该合数的最小质因数
//为什么用i判断,原因已经讲过了,每一个合数及这个合数的任意倍数都有相同的最小质因数
//如果此时的prime[j]能被i整除,说明它就是i的最小质因数,也是i*prime[j]的最小质因数
//因为欧拉筛的特点就是每个合数只被它的最小质因数筛
//为了防止被重复筛,所以此时必须break
}
}
}
问题1:为什么说i的作用是用做倍数呢,因为所有的合数 都是质数*质数,或质数*合数构成的,
我们要筛去12,因为12=2*6,质数表中有12的最小质因数2,我们要筛掉12,必须要搭配一个6,才能做到精确筛除(精确制导),否则12有可能被3*4筛去,但3不是12的最小质因数。
然后,我们用6来假设,如果i=6,那么说明i已经遍历了2~5,也就是说质数表中有了2,3,5三个质数(这是外层循环i的第一个作用,存质数)
第一次内层循环,我们标记了 2*6 = 12. 筛去了12.
然后 if语句判断2是不是6的最小质因数 ,
如果是,那么2也是12的最小质因数,所以我们必须终止内层循环,保证每个合数只被其最小质因数筛去。
假如没有这个if条件 我们可能还会筛掉 3*6=18,5*6=30,那么当后续i循环到9的时候,又会重复筛去 2*9=18.
好了,关于欧拉筛,埃氏筛,我们就讲到这里,字数很多,因为我必须要是自己理解了之后才发出来的,所以想了很多东西。
希望大家有所收获。