是你流的泪晕开——欧拉筛,线性筛

题主在刷题过程中碰到一个筛素数的问题:看题解的时候发现了这两种筛法,下面为大家一一讲解一下。

最传统的查找一个数是否为素数:

​
​
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(  n*ln(ln^{n})  ).

但是埃氏筛也不是最完美的,我们可以观察到,同一个数,可能是多个素数的倍数,所以它会被重复筛出,比如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.

好了,关于欧拉筛,埃氏筛,我们就讲到这里,字数很多,因为我必须要是自己理解了之后才发出来的,所以想了很多东西。

希望大家有所收获。

  • 10
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

louisdlee.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值