大区间素数的求解

目录

一、朴素算法:

二、优化的素数筛法

1、埃氏筛法(埃拉托斯特尼筛法)

2、欧拉筛法


很多时候我们解决问题的时候会需要产生某个区间的素数的模板,那这个时候如果区间很小,如何求解都无所谓;但如果这个区间很大,就涉及到要优化求解素数模板的算法,否则会额外降低效率。

一、朴素算法:

        最直接的想法就是遍历这个区间内的所有数字并逐个利用试除法判断是否是素数加以统计。我们就用1~n这个区间为例:

int primes[MAXN]; //用来存储1~n之间的素数
int c = 0; //1~n之间素数的个数
for(int i = 2; i <= n; i++)
{
    bool flag = true;
    for(int j = 2; j * j < i; j++)
    {
        if(i % j == 0)
        {
            flag = false;
            break;
        }
    }
    if(flag) 
    {
        primes[c++] = i;
    }
}

        这个算法的复杂度是O(n^{\frac{3}{2}}),n的范围较大时显然不适用。求解一个素数模板甚至会超出解决问题所需要的时间。此时我们用到下面的两种方法。

二、优化的素数筛法

优化的角度与思路都是一样的,我们可以用已有的素数作为因子把它的倍数(一定是合数)筛掉,这也是优化的方法为什么叫做筛法的原因。

1、埃氏筛法(埃拉托斯特尼筛法)

        每统计出一个质数p,就把他的倍数(1~x倍直到x * p < maxn)全部标记为合数,下次筛到这个数直接跳过,没被筛掉的就是下一个要统计的素数。代码如下:

int prime[MAXN + 1]; //存储1~n之间的所有素数
int visit[MAXN + 1]; //标记数组值为0的下标是素数,为1的是合数
int cnt; //素数个数
void solve(int n) //统计1~n之间的素数
{
    cnt = 0;
    memset(visit, 0, sizeof(visit)); //先全部置为0(素数),后续会筛掉
    for(int i = 2; i <= n; i++)
    {
        if(!visit[i]) //统计到了一个素数
        { 
            prime[cnt++] = i;
            for(int j = 2 * i; j <= n; j++) //筛的过程,这里可以优化,下面讲解
            {
                visit[j] = 1;
            }
        }
    }
}

        实际上,上面有一处我标记了可以优化,那就是利用已找出的一个素数筛掉它的倍数的过程。我们呢每次都从2 * i开始筛,我举个例子大家一下子就看明白哪里是多余的了:我们

  • 用2这个素数筛掉了2*2, 2*3, 2*4, 2*5, ... , 2*10(2*2*5=4*5), ...,  2*k1;
  • 用3这个素数筛掉了3*2, 3*3, 3*4, 3*5, ..., 3*k2;
  • 用5这个素数筛掉了5*2, 5*3, 5*4, 5*5, ..., 5*k2;

实际上,在以3和5为基准去筛的时候,很多都是之前被筛过了的,因此每次我们只需要从i * i起筛,而并不需要从2 * i起筛。

        这个算法的复杂度大致是O(\sqrt{n}log(log(\sqrt{n})))+O(n\sqrt{n})

2、欧拉筛法

       虽然埃氏筛法已经能解决大部分问题,但是其仍做了许多重复筛选的工作,比如12被2和3同时筛过(即使我们优化过)。因此,对于更大范围的区间,我们需要使用复杂度更低的筛法——欧拉筛。

        欧拉筛能使每个合数仅被筛一次,这样,我们可以在O(n)的复杂度内统计出1~n之间的所有素数。先贴代码,边看边讲解:

int prime[MAXN]; //存储素数的数组
int visit[MAXN]; //标记数组
int cnt; //统计出的素数个数
void solve(int n)
{
    cnt = 0;
    memset(visit,0, sizeof(visit));
    for (int i = 2; i <= n; i++) 
    {
        if (!visit[i]) 
        {
            prime[cnt++] = i; //纪录素数
        }
        for (int j = 0; j < cnt && i * prime[j] <= n; j++) 
        {
            visit[i * prime[j]] = 1;
            if (i % prime[j] == 0) 
            {
                break;
            }
        }
    }
}

        欧拉筛有两处要讲解:

  • 第二重循环的作用:埃氏筛法中,我们用一个素数的倍数去筛合数,在这里,我们用已筛出的素数作为因子去筛合数。
  • 为什么要在i % prime[j] == 0时退出循环:如果i % prime[j] == 0,即当 i是prime[j]的倍数时,i = k * prime[j],如果继续筛prime[j + 1],那么i * prime[j + 1] = prime[j] * k * prime[j + 1],当i = k * prime[j+1]时会重复,所以才跳出循环。用一个例子帮大家理解一下:当i = 8 ,j = 1时,prime[j] = 2,如果不跳出循环,prime[j + 1] = 3,对应到上面的式子,8 * 3 = 2 * 4 * 3 = 2 * 12,在i = 12时仍然会筛一次。实际上这涉及到一些数学知识,为什么这样就能只筛一次,笔者也不求甚解了。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

落英S神剑

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

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

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

打赏作者

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

抵扣说明:

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

余额充值