基础数论算法(5) 素数的判定

有关素数的研究很久之前就已经开始,根据科(xuan)学研究,数质数有助于睡眠。那么如何高效的让计算机数质数,跑得更快?这就是我们要探讨的主要内容。


O( n )判定法

数据比较小的时候,O( n )判断法就够了。方法就是从2枚举到 n 。(顺便吐槽一句数学必修三居然枚举到n-1,差评)
实现:

bool isPrime(int x){
    if(x<2)    return false;
    for(int i=2;i*i<=m;i+=(i==2?1:2))
        if(m%i==0) return false;
    return true;
}

费马小定理判断法

有一个神奇的定理叫费马小定理。如果一个奇素数p,有 apa(mod  p) ,其中 1a<p
它的逆命题是错误的,但是如果我们rand()很多数出来带进逆命题检验,结果如何?事实证明,随机数目在1000次左右时,准确率可以达到99.7%左右。实现:

#include 
using namespace std;
typedef long long LL;
int m[]={3,7,13,37,23};
LL ksm(LL a,LL m,LL n){
    if(m==0) return 1;
    if(m==1) return a%n;
    LL w=ksm(a,m>>1,n);
    w=w*w%n;
    if(m&1) w=w*a%n;
    return w;
}
bool fm(LL n){
    if(n==2) return true;
    for(int i=0;i<5;++i){
        int a=m[i];
        if(ksm(a,n,n)!=a) return false;
    }
    return true;
}
int main(){
    LL n;
    scanf("%d",&n);
    if(fm(n)) printf("Yes\n");
    else printf("No\n");
    return 0;
}

我闲来无事将这个代码和直接判定对拍了一下,卡在了2821上。为什么呢?
有一类特殊的数:Carmichael数。这类数的特征是,对 a< p上述性质都是成立的,这其中最小的是561.
因此,费马小定理测试得到了毁灭性打击。不过这仍然不失为一种容易实现的判断方法万一出题人卡这东西呢?说的好像因为出题人可能卡SPFA你就不用了似的为此而开发出的一种新的判断方法,就是Miller-Rabin测试。


Miller-Rabin测试法

即便对RP的需要有所下降,这仍然是一个拼RP的做法,但是你没有选择。因为这是最为快速且高效的判断法。
Miller-Rabin筛法基于Miller-Rabin定理。

Miler-Rabin定理
若n为素数,取 a<n ,设 n1=d2r
要么 ad1(mod  n) ,要么 0ir使ad2i1(mod  n)

该定理的逆命题是不成立的,但是如果我们多取几个素数,逆命题正确的概率就会大大上升。与费马小定理不同之处是,MR定理不存在合数满足这一性质。
如果取的是2,3,5,7这四个,而在 2.5×1013 以内只有3215031751这个数会测试失误。
代码参考了
https://github.com/Psycho7/Miller-Rabin/blob/master/CPP/Miller-Rabin.cpp
在下找了半天之后认为写的比较精悍的一个

#include 
using namespace std;
typedef long long LL;
int pri[]={2,3,5,7,11,13,17};
LL ksm(LL a,LL n,LL mod){
    if(n==0) return 1;
    if(n==1) return a%mod;
    LL ans=1;
    while(n){
        if(n&1) ans=(ans*a)%mod;
        a=a*a%mod;
        n>>=1;
    } 
    return ans;
}
bool MR(LL x){
    if(x==2) return true;
    if((x<2)||!(x&1)) return false;
    LL d=x-1;
    while(!(d&1))d>>=1;
    LL td,t;
    for(int i=0;i<7;++i){
        if(pri[i]>=x) break;
        td=d;
        t=ksm(pri[i],td,x);
        while((td!=x-1)&&(t!=1)&&(t!=x-1)){
            t=t*t%x;
            td<<=1;
        }
        if((t==x-1)||(td&1)) ;
        else return false;
    }
    return true;    
}
int main(){
    LL k;
    cin>>k;
    cout<<(MR(k)?"Yes":"No");
}

说实话我是不太懂这个代码究竟是如何实现的,所以实在不行就先背会吧。


基本上相信自己的rp和出题人的良心的话跑费马小定理测试一般不会出问题,毕竟家里那本书在MR测试下堂而皇之的写着费马测试的代码,实在是不太想吐槽。当然,数据比较小的话,朴素算法也是没有什么问题的,关键是不容易出错,不要把8个TLE变成20个WA比什么都强。
复杂度的话,费马是O(klogn)的,MR是O( klog3n )(别吐槽这个鬼畜的三次方……),别忘了一定要写快速幂。

你可能感兴趣的:(数论)