zoukankan      html  css  js  c++  java
  • 关于素数和素数筛小记

    (本文大量参考算法竞赛进阶指南)

    0、定义

    质数定义为:若一个大于 $1$ 的正整数,无法被除了 $1$ 和它自身以外的其他任何正整数整除,即称该数为质数。

    相应的,剩下的正整数,除了 $1$ 之外,称为合数。

    应当注意的一点:质数的数量不多,分布稀疏,对于一个足够大的正整数 $N$,不超过 $N$ 的质数大约有 $frac{N}{ln N}$ 个,即每 $ln N$ 个正整数中,大约有一个质数。

    1、素数判定

    1.1、试除法

    对于要判定的正整数 $N$,用 $2 sim lfloor  sqrt{N} floor$ 进行试除,这是很朴素自然的一种做法,时间复杂度 $O(sqrt(N))$。

    1.2、费马素性检验

    费马小定理:如果 $p$ 是一个质数,且正整数 $a$ 与 $p$ 互质,则有 $a^{p-1} ≡ 1 pmod p$。

    这个定理的逆命题,在一定程度上可以用来检测素数,存在反例:

    虽然 $2^{340} mod 341 = 1$,但 $341=11 imes 31$。后来,人们又发现了 $561, 645, 1105$ 等数都表明 $a=2$ 时费马小定理的逆命题不成立。因此,引出伪素数的概念:若 $n$ 能整除 $2^{n-1}-1$(换句话说,$2^{n-1}-1$ 能被 $n$ 整除),但同时 $n$ 又是合数,那么 $n$ 就是伪素数。因此,伪素数不一定是素数,不满足 $2^{p-1} mod p = 1$ 的一定是合数。

    当然,$a=3,4,5,cdots$ 时,又会有不一样的情况,因此称:满足 $a^{n-1} mod n = 1$的合数 $n$ 叫做以 $a$ 为底的伪素数

    因此,随机选择若干个小于待测数的正整数作为底数 $a$ 进行若干次测试,只要有一次没有通过测试就立刻可判定为合数。这就是费马素性检验。

    再有关于卡迈克尔数的定义:对于合数 $n$,如果对于所有满足与 $n$ 互质的正整数 $a$,都有同余式 $a^{n-1} ≡ 1 pmod n $ 成立,则合数 $n$ 为卡迈克尔数。尽管卡迈克尔数很少(在 $1 sim 1e8$ 范围内的整数中,只有 $255$ 个卡迈克尔数),但依然使得我们无法使用费马素性检验。

    1.3、Miller-Rabin素性检验

    定理:如果 $p$ 是素数,$x$ 是小于 $p$ 的正整数,且 $x^2 ≡ 1 pmod p$,那么要么 $x=1$,要么 $x=p-1$。

    证明:因为 $x^2 ≡ 1 pmod p$ 相当于 $x^2 - 1$ 能被 $p$ 整除,也即 $p$ 能整除 $(x+1)(x-1)$。由于 $p$ 是素数,那么只可能是 $x-1$ 能被 $p$ 整除,此时 $x=1$,或 $x+1$ 能被 $p$ 整除,此时 $x=p-1$。

    因此,将上述定理应用于伪素数 $341$ 的费马素性检验:

    由于 $2^{340} mod 341 = 1$,即 $(2^{170})^2 ≡ 1 pmod{341}$,假定 $341$ 是素数,那么应当有 $2^{170} ≡ pm 1 pmod{341}$,算得 $2^{170} mod 341$ 确实等于 $1$ 时,我们可以继续查看 $2^{85} mod 341$ 的结果。我们发现,$2^{85} mod 341=32$,因此 $341$ 不是素数。

    Miller-Rabin素性检验同样有较小的概率把合数错判为质数,我们把可以通过以 $a$ 为底的Miller-Rabin素性检验的合数称作以 $a$ 为底的强伪素数

    给出一个Miller-Rabin素性检验模板:

    namespace MR
    {
        int test[4]={2,3,5,7};
        ll Pow(ll a,ll n,ll mod)
        {
            ll res=1, base=a%mod;
            while(n)
            {
                if(n&1) res*=base, res%=mod;
                base*=base, base%=mod;
                n>>=1;
            }
            return res%mod;
        }
        bool Test(int p)
        {
            if(p<=1) return 0;
    
            int t=p-1, cnt=0;
            while(t%2==0) t>>=1, cnt++;
    
            for(int i=0;i<4;i++)
            {
                if(p==test[i]) return 1;
                ll now=Pow(test[i],t,p), nxt;
                for(int j=1;j<=cnt;j++)
                {
                    nxt=(now*now)%p;
                    if(nxt==1 && now!=1 && now!=p-1) return 0;
                    now=nxt;
                }
                if(now!=1) return 0;
            }
            return 1;
        }
    }
    View Code

    HIT1356上进行了验证,在洛谷3383上进行了验证,手动验证在$[0,1e8]$ 范围内全部正确。

    2、素数筛

    2.1、埃筛

    埃筛基于这样一个很简单的想法:对于任意质数 $x$,其倍数 $2x,3x,cdots$ 均不是质数。筛不超过 $N$ 的素数,埃筛时间复杂度是 $O(N log log N)$。

    代码:

    const int MAX=2e7;
    int tot,prm[MAX/15];
    bool noprm[MAX+3];
    void Erato()
    {
        tot=0;
        noprm[0]=noprm[1]=1;
        for(int i=2;i<=MAX;i++)
        {
            if(noprm[i]) continue;
            prm[tot++]=i;
            for(int j=i;j<=MAX/i;j++) noprm[i*j]=1;
        }
    }

    (注:开数组时注意不超过 $N$ 的质数大约有 $frac{N}{ln N}$ 个)

    http://acm.nefu.edu.cn/JudgeOnline/problemShow.php?problem_id=2验证了上述代码。

    2.2、欧拉筛

    欧拉筛是线性筛,筛不超过 $N$ 的素数其时间复杂度是 $O(N)$。

    不难发现埃筛会对一些合数重复标记,因此考虑优化,对于任意一个合数,只用其最小质因子将其筛去。

    假设数组 $v$ 是记录每个数的最小质因子的,因此欧拉筛可以描述为:

    ①依次考虑 $2 sim N$ 的每个数 $i$;

    ②若 $v[i]=i$,即最小质因子是其本身,那么 $i$ 即为质数。

    ③遍历不超过 $v[i]$ 的每个素数 $p$,令 $v[i*p]=p$,即整数 $i*p$ 的最小质因子为 $p$。

    代码大概可以描述成:

    void Euler()
    {
        tot=0;
        for(int i=2;i<=MAX;i++)
        {
            if(v[i]==0) v[i]=prm[tot++]=i;
            for(int j=0;j<tot;j++)
            {
                if(prm[j]>v[i] || prm[j]>MAX/i) break;
                v[i*prm[j]]=prm[j];
            }
        }
    }
    View Code

    然后,我们发现并不一定需要维护最小质因子,也可以优化成如下代码:

    const int MAX=2e7;
    int tot,prm[MAX/15];
    bool noprm[MAX+3];
    void Erato()
    {
        tot=0;
        noprm[0]=noprm[1]=1;
        for(int i=2;i<=MAX;i++)
        {
            if(!noprm[i]) prm[tot++]=i;
            for(int j=0;j<tot && prm[j]<=MAX/i;j++)
            {
                noprm[i*prm[j]]=1;
                if(i%prm[j]==0) break;
            }
        }
    }

    http://acm.nefu.edu.cn/JudgeOnline/problemShow.php?problem_id=2验证了上述代码。

    2.3、两种筛法的比较

    其实埃筛 $O(N log log N)$ 的时间复杂度很接近线性,我们可以对埃筛和欧拉筛进行耗时的比较:

    令 $N=5e6$,分别运行50次埃筛和欧拉筛,耗时比较:

    令 $N=6e6$,分别运行50次埃筛和欧拉筛,耗时比较:

    大概可以认为,大约在 $N le 5e6$ 时,埃筛更优,大约在 $N > 5e6$ 时,欧拉筛更优。

  • 相关阅读:
    第三次作业
    第二次作业
    第一次作业
    实验二
    第一次试验
    第五次作业
    第四次作业
    第三次作业
    第二次作业
    第一次作业
  • 原文地址:https://www.cnblogs.com/dilthey/p/7571967.html
Copyright © 2011-2022 走看看