质数算法略解

时间:2021-12-15 13:26:03

定义

若一个正整数无法被除了1和它自身之外的任何自然数整除,则该数为质数,否则该数为合数。

在整个自然数集合中,质数的数量不多,分部比较稀疏,对于一个足够大的整数N,不超过N的质数大约有 \(N / \ In N\)个,即每\(\ In N\)个数中大约有一个质数

一、质数的判定

1、试除法

证明解释略(不会的noip都别想考)

inline bool is_prime(register int n)
{
    for(register int i=2;i<=sqrt(n);++i)
        if(n%i==0)
            return false;
    return true;
}

这种算法需要扫描\(2~\sqrt N\)之间的所有整数,依次检查他们能否整除N,复杂度为\(O(\sqrt N)\)

Miller-Robbin

试除法速度太慢,我们再学一下Miller-Robbin算法

Miller-Robbin算法度娘的解释

Miller-Rabin算法是目前主流的基于概率的素数测试算法,在构建密码安全体系中占有重要的地位。通过比较各种素数测试算法和对Miller-Rabin算法进行的仔细研究,证明在计算机中构建密码安全体系时, Miller-Rabin算法是完成素数测试的最佳选择。通过对Miller-Rabin 算 法底层运算的优化,可以取得较以往实现更好的性能。[1]  随着信息技术的发展、网络的普及和电子商务的开展, 信息安全逐步显示出了其重要性。信息的泄密、伪造、篡改 等问题会给信息的合法拥有者带来重大的损失。在计算机中构建密码安全体系可以提供4种最基本的保护信息安全的服 务:保密性、数据完整性、鉴别、抗抵赖性,从而可以很大 程度上保护用户的数据安全。在密码安全体系中,公开密钥 算法在密钥交换、密钥管理、身份认证等问题的处理上极其有效,因此在整个体系中占有重要的地位。目前的公开密钥 算法大部分基于大整数分解、有限域上的离散对数问题和椭 圆曲线上的离散对数问题,这些数学难题的构建大部分都需 要生成一种超大的素数,尤其在经典的RSA算法中,生成的素数的质量对系统的安全性有很大的影响。目前大素数的生 成,尤其是随机大素数的生成主要是使用素数测试算法,本 文主要针对目前主流的Miller-Rabin 算法进行全面系统的分析 和研究,并对其实现进行了优化

实际就是判断质数的一种算法

Miller Rabin算法的依据是费马小定理的一个推论

\(a ^ {P-1} \equiv 1(mod P)\)

这个式子什么时候成立呢,就是当P是质数时,这个式子才成立

这样就可以判断P是否是质数

但是后来被人举出了反例

这是否意味着利用费马小定理的思想去判断素数的思想就是错误的呢?

答案是肯定的。

但是如果我们可以人为的把出错率降到非常小呢?

比如,对于一个数,我们有99.99999%的几率做出正确判断,那这种算法不也很优越么?

于是Miller Rabin算法诞生了!

首先介绍一下二次探测定理

如果P是质数且\(a^2 \equiv 1(\mod P)\),那么\(a \equiv \pm 1(\mod P)\)

这个很好证明就不写了qaq

这个定理和素数判定有什么用呢?

首先,根据Miller Rabin算法的过程

假设需要判断的数是p

我们把p−1分解为\(2^k*t\)的形式

当p是素数,有\(a^{2^k*t} \equiv 1(\mod p)\)

然后随机选择一个数a,计算出\(a^t(\mod p)\)

让其不断的自乘,同时结合二次探测定理进行判断

如果我们自乘后的数\((\mod p)=1\),但是之前的数\((\mod p)\neq \pm 1\)

那么这个数就是合数(违背了二次探测定理)

这样乘k次,最后得到的数就是\(a^{p-1}\)

那么如果最后计算出的数不为1,这个数也是合数(费马小定理)

那么正确性如何呢?

老祖宗告诉我们,若p通过一次测试,则p不是素数的概率为2525%

那么经过t轮测试,p不是素数的概率为\(\frac{1}{4^t}\)

我习惯用2,3,5,7,11,13,17,19这几个数进行判断

在信息学范围内出错率为0%(不带高精)

注意在进行素数判断的时候需要用到快速幂。。

这个应该比较简单,就不细讲了

完整代码(Luogu P3383 【模板】线性筛素数

#include <bits/stdc++.h>
#define ll long long
using namespace std;
inline char nc(){
    static char buf[100000],*p1=buf,*p2=buf; 
    return p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++; 
}
inline int read()
{
    register int x=0,f=1;register char ch=nc();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=nc();}
    while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=nc();
    return x*f;
}
int N,M;
int Test[10]={2,3,5,7,11,13,17};
inline int Pow(register int a,register int p,register int mod)
{
    int base=1;
    for(;p;p>>=1,a=(1ll*a*a)%mod)
        if(p&1)
            base=(1ll*base*a)%mod;
    return base%mod;
}
inline bool Query(register int P)
{
    if(P==1)
        return 0;
    int t=P-1,k=0;
    while(!(t&1))
        ++k,t>>=1;
    for(register int i=0;i<4;++i)
    {
        if(P==Test[i])
            return 1;
        ll a=Pow(Test[i],t,P),nxt=a;
        for(register int j=1;j<=k;++j)
        {
            nxt=(a*a)%P;
            if(nxt==1&&a!=1&&a!=P-1)
                return 0;
            a=nxt;
        }
        if(a!=1)
            return 0;
    }
    return 1;
}
int main()
{
    N=read(),M=read();
    while(M--)
        puts(Query(read())?"Yes":"No");
    return 0;
}

二、质数的筛选

1.Eratosthenes筛法

我们有这样的想法,当一个数p是质数时,2p,3p,4p···就不是质数

另外我们从\(p^2\)开始标记

因为中间的数有比p更小的质因子

我们就珂以写出Erotasthenes筛法

复杂度为\(O(N \log \log N)\)

inline void prime(register int n)
{
    memset(v,0,sizeof(v));
    for(register int i=2;i<=n;++i)
    {
        if(v[i])
            continue;
        write(i),puts("");
        for(register int j=i;j<=n/i;++j)
            v[i*j]=1;
    }
}

2.欧拉筛

但是Erotasthenes筛法还是会背重复标记,比如12会被2和3都标记到

所以我们在标记合数的过程中,每次只要向现有的数(注意:不只是质数)乘上一个质因子,并且它是合数最小质因子

这样每个数只会被筛到1次

复杂度为\(O(N)\)

完整代码(Luogu P3383 【模板】线性筛素数

#include <bits/stdc++.h>
#define N 10000005
using namespace std;
inline char nc(){
    static char buf[100000],*p1=buf,*p2=buf; 
    return p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++; 
}
inline int read()
{
    register int x=0,f=1;register char ch=nc();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=nc();}
    while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=nc();
    return x*f;
}
int v[N],prime[N];
inline void primes(register int n)
{
    memset(v,0,sizeof(v));
    int m=0;
    for(register int i=2;i<=n;++i)
    {
        if(v[i]==0)
        {
            v[i]=i;
            prime[++m]=i;
        }
        for(register int j=1;j<=m;++j)
        {
            if(prime[j]>v[i]||prime[j]>n/i)
                break;
            v[i*prime[j]]=prime[j];
        }
    }
}
int main()
{
    int n=read(),m=read();
    primes(n);
    while(m--)
    {
        int x=read(); 
        puts(v[x]==x?"Yes":"No");
    }
    return 0;
 }