筛质数 (包含线性筛的理解证明)

质数的判断以及筛选

一质数

​ 首先我们要明确什么是是质数。质数,也可以叫做素数,我们可以理解为给定一个数x,那么 ∀ i ( i ∈ [ 2 , n − 1 ] ) \forall{i} (i\in[2,n-1]) i(i[2,n1]),都存在 x % i ≠ 0 x\% i\neq0 x%i=0,那么,我们就称这个数x为质数。

二质数筛选方法

1.朴素方法

​ 首先,是最为朴素的算法,很简单,按照定义直接暴力搜索就行了,枚举从2到n-1的所有数字,取余运算完事了,但是,这样筛质数的时间复杂度是 O ( n 2 ) O(n^2) O(n2),很多的情况的,我们要打的表要到 1 e 7 1e^7 1e7,很明显这个方法不适用,但是,还是贴出来代码看一下,因为有的题目数据要求不这么严格,这个方法是最好想的。

​ 但是,即使是最为朴素的方法,优化也是很好想象的,比如,12是个合数(不是质数就是合数,也就是12前面有质因子),2和6是两个质因子,3和4是两个质因子,我们发现,质因子是成双成对出现的,两个质因子肯定是单调不减的,也就是说,我们没有必要搜到n-1,只需要搜到 n \sqrt{n} n 就可以了。因为如果搜到 n \sqrt{n} n 还没有质因子,那么右半边一定也没有质因子。

​ 那么这样筛选质数的方法就可以优化成 O ( n n ) O(n\sqrt{n}) O(nn )虽然有些问题还是不够。

  if(x<=1)
    return 0;
    for(int i=2;i<=x/i;i++)
    {
        if(x%i==1)
        return 0;
        


    }
    return 1;
    

小tips:

  • 1也是合数,所以要特判,这个问题看似很小,但是因为这个点无法AC真的很痛苦
  • 就是对于for循环里面的条件 还有另外两种写法也是正确的 i<=sqrt(x),这个不是很好,因为sqrt我个人认为底层可能是用浮点数二分来实现的,这个肯定是对的,但是相对来说速度比较慢
  • 还有一种就是我以前喜欢使用的 i*i<=x 这个也不是很提倡,因为当i比较大的时候,可能会爆int ,所以建议背板子就背正确的。

2.埃氏筛打表质数

​ 对于埃氏筛,我们可以先了解一下没有优化的埃氏筛,没有优化的埃氏筛的思想是对于一个数字,他的倍数肯定是合数,比如我们要求10以内的质数,首先进入循环,第一个数是2,他是质数直接加入到答案当中,然后把10以内的2的质数全部打上标记,表示这些数字全是质数。

​ 下面会有一张图 来解释一下这个代码的过程

​ 注意,在代码当中,prime【i】为1代表这个数是合数,否则就为质数,ans数组记录的是所有素数的集合 。

#没有优化的埃氏筛
#include<iostream>
using namespace std;
const int N=1e8+100;
int prime[N];
int cnt;
int ans[N];
void get_prime(int n)
{
    prime[1]=1;
    for(int i=2;i<=n;i++)
    {
        if(prime[i]==0)
        {

            ans[cnt++]=i;
        }
        for(int j=i+i;j<=n;j+=i)
        {
            prime[j]=1;

        }
    
    }

}
int main()
{

    
    int n;
    cin>>n;
    get_prime(n);
    cout<<cnt<<endl;
    return 0;
}

​ 画一下图就如下图所示:

在这里插入图片描述

​ 如图所示,黑色表示第一次筛出来的合数被删除了,红色表示从3开始筛选出的合数被删除了,蓝色表明从4开始筛选出的合数被删除。这种欧拉筛的时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn)

​ 但是,我们可以看出来一个问题,比如,我们在2筛选过的数,在4又被筛选了一遍,而这显然不是我们想要的,所以,我们可以优化,就是每次只筛选质数,也就把第二个for循环放到第一个if里面,这样可以把复杂度优化到 O ( n l o g l o g n ) O(nloglogn) O(nloglogn)

​ 代码如下:

#include<iostream>
using namespace std;
const int N=1e8+100;
int prime[N];
int cnt;
int ans[N];
void get_prime(int n)
{
    prime[1]=1;
    for(int i=2;i<=n;i++)
    {
        if(prime[i]==0)
        {

            ans[cnt++]=i;
            for(int j=i+i;j<=n;j+=i)
        {
            prime[j]=1;

        }
        }
        
    
    }

}
int main()
{

    
    int n;
    cin>>n;
    get_prime(n);
    cout<<cnt<<endl;

    return 0;
}

3.线性筛

​ 但是,即使是.埃氏筛,在一些问题当中依旧会tle,所以,我们就要使用 线性筛,线性筛的时间复杂度是 O ( n ) O(n) O(n)的,相对来说比较优秀 .

​ 代码如下

#include<iostream>
using namespace std;
int n;
const int N=1e8+100;
int ans[N],prime[N];
int cnt;
void get_prime(int n)
{
    prime[1]=1;
    for(int i=2;i<=n;i++)
    {
        if(prime[i]==0)
        {
            ans[cnt++]=i;
        }
        for(int j=0;ans[j]<=n/i;j++)
        {	
            prime[ans[j]*i]=1;
            if(i%ans[j]==0)
            {

                break;
            }


        }




    }



}
int main()
{

    cin>>n;
    get_prime(n);
    cout<<cnt<<endl;


    return 0;
}

​ 其实,线性筛的优化我们也可以管中窥豹在前面看到,优化之后的欧拉筛可以优化掉2和4对8的重复筛选,却不可以优化掉2和3对于6的重复筛选,而我们的线性筛选就是用最小质因子来筛选出质数。因为,每个数的最小质因子只有一个,所以我们每个数只会被筛选一次。

​ 这个算法的核心就是每个数字都只会被他的最小质因子筛选出来 那么这是为什么那 。

我们首先要知道一个前提 那就是 对于任意一个数 n n n都有
n = p 1 α 1 ∗ p 2 α 2 ∗ … ⋯ ∗ p x α x ( 1 ) n=p_1^{\alpha_1}*p_2^{\alpha_2}*\ldots\dots*p_x^{\alpha_x} (1) n=p1α1p2α2…⋯pxαx1
​ 其中 p 1 … p k p_1\ldots p_k p1pk为这个数的质因子

​ 然后我们再去分情况讨论 先来证明 ans[j]*i当中的ans[j]一定是他们乘积的最小质因子

  • 首先就是当i%ans[j]==0的时候 这个时候 我们 首先可以确定 ans[i]是 i的最小质因子 因为我们的 j是从小到大遍历的,也就是ans[j]是单调递增的

    ​ 那么 ans[j]就是i的多项式展开当中的 p 1 p1 p1 那么 primes[j]*i多项式展开中的 p 1 p1 p1也一定是ans[j] 。我们可以这么理解 既然ans[j]式i多项式展开当中的 p 1 p1 p1 那么 他们两个相乘只不过是在i的多项式当中 p 1 p1 p1的指数加一

    ​ 当i%ans[j]==0 的时候得证

  • 第二种情况就是 i%ans[j]!=0的时候 这种情况我们可以这么理解 等于0的时候就是i的最小质因子 ,那么没找到之前 一定比i的最小质因子 p 1 p1 p1要小(还是那个原因 j是从小往大遍历) 那么他们两个相乘不过是在i的多项式展开当中加入了一项比 p 1 p1 p1还小的质因子成为了新的 p 1 p1 p1 那么相乘之后 我们的ans[j]也肯定是 最小质因子

综上 ans[j]一定是最小质因子得证

​ 然后就是说一下为什么能全筛选出来 原因其实很简单 就是每个数一定都要最小质因子 这个最小质因子是他本身就是质数 其他的 我们都能筛选出来

​ 这个是比较直观的理解 我们可以假设给定一个合数n 他一定是存在不等于他的最小质因子 p j p_j pj 当我们在枚举到 n之前 一定枚举过n/ p j pj pj 也就是说n已经被筛过

​ 最后就是说一下细节问题 就是代码当中 for(int j=0;ans[j]<=n/i;j++) 这个语句j<cnt是不需要加的 因为 如果i为合数 肯定会被最小质因子break掉 当i是质数 ans[cnt-1]之后的数肯定是0 会结束循环

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值