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

来源:互联网 发布:软件代理合同范本 编辑:程序博客网 时间:2024/05/16 15:22

有关素数的研究很久之前就已经开始,根据科(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 <bits/stdc++.h>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 <bits/stdc++.h>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)(别吐槽这个鬼畜的三次方……),别忘了一定要写快速幂。

原创粉丝点击