大素数判断算法 Miller_Rabin

引言:

如果你遇见很多个非常大的数n,有10的18次方这么大,让你来判断是不是素数,你有什么快速的方法呢?

用普通的判断会超时,用筛法又筛不到,这时候就可以用上大素数判断算法。

其实这个算法理解原理后很简单,我也就wa了11次


 

原理部分:

首先我们知道,对于一个质数P,他一定满足费马小定理,

  即   a^(p-1)≡1(mod p)

那么我们反过来想,对于任意一个数n,若它满足 a^(n-1)≡1(mod n),那么它是不是就是一个质数呢?

答案是 不一定,但是 在绝大多数的状况下,n就是个质数。因此,对于一个很大的数n,我们可以随便取一个a,然后用

快速幂计算a^(n-1)mod n,看最后的结果是不是1,以此来判断是不是质数。

但这样还是有很大的误差,为此我们引入一个新定理——二次探测原理。

若P是一个质数,且满足0<x<p、x^2 mod p =1,则可以推断出x一定等于1或p-1。

因为除了2,所有的质数都是奇数,所以n若是偶数且不为2,那他一定不是质数,若他是奇数,则(n-1)一定是偶数,

我们就可以把a^(n-1)改写成(a^(n-1)/2)2,就满足了二次探测原理的x^2的形式,我们就可以用二次探测原理来验证n是不是质

数,即若x^2%n=1,但x不等于1或n-1,则n一定不是一个质数。

更关键的是,若(n-1)/2仍是一个偶数,我们还可以把a^(n-1)/2改写乘某个数的平方,以此类推。

即把n-1化为2* d  ,   a^(n-1)=

 

 

 

 

 

我们就可以在求 a^(n-1)mod n的过程中,多次验证二次探测原理。

通过上面两个原理,我们再多用几个a验证,判断n是不是质数准确率就可以达到99.9999999999999....%。

另外有一个结论:如果我们选择2,3,7,61,24251,9875321这6个质数作为a的话,在10^14内不存在误判。

好了,如果没有看懂上面的的理论就看下面的代码吧。


 代码部分:

inline LL quick_mul(LL a,LL b,LL m) //快速乘 
{
    ll ans = 0;
    a %= m;
    b %= m;
    while (b) {
        if (b & 1) {
            ans = (ans + a) % m;
        }
        a = (a + a) % m;
        b >>= 1;
    }
    return ans;
}

LL quick_pow(LL a,LL b,LL m) //快速幂 
{
    LL res=1;
    a%=m;
    while(b)
    {
        if(b&1) res=quick_mul(res,a,m);
        a=quick_mul(a,a,m);
        b>>=1;
    }
    return res;
}
bool Miller_rabin(LL n,int num)
{
//先将特殊情况判断一下
    if(n==2||n==3) return true;
    if(n%2==0||n==1) return false;
    srand((unsigned)time(NULL)); //为接下来a的随机取值用 
    LL d=n-1;
    int s=0;
    while(!(d&1)) s++,d>>=1;//若d的二进制的最后一位不是1,则说明d还是偶数 
    for(int i=0;i<num;i++)
    {
        LL a=rand()%(n-2)+2;//2~n-1;
        LL x=quick_pow(a,d,n), y=0;
        for(int j=0;j<s;j++)//一共平方s次 
        {
            y=quick_mul(x,x,n);//先平方 
            if(y==1&&x!=1&&x!=(n-1)) return false;//验证二次探测原理 
            x=y;
        }
        if(y!=1) return false;//不满足费马小定理,那就肯定不是质数
    }
    return true;
}

另外补充一点就是,对于上面的快速乘,还有一种更快速的写法

inline LL quick_mul(LL a,LL b,LL m)
{
    LL t=(ld)a/m*b;
    LL res=(ull)a*b-(ull)t*m;
    return (res+m)%m;
}

利用的是 相乘溢出的是高位,但由于取模我们只需要的是低位。

还有就是a*b%m=a*b-(a*b/m)*m 

posted @ 2020-07-31 21:04  Rain_luo  阅读(678)  评论(0编辑  收藏  举报