与数论的厮守01:素数的测试——Miller Rabin

  看一个数是否为质数,我们通常会用那个O(√N)的算法来做,那个算法叫试除法。然而当这个数非常大的时候,这个高增长率的时间复杂度就不够这个数跑了。

  为了解决这个问题,我们先来看看费马小定理:若n为素数,a与n互质,则an-1Ξ1(mod n)。于是有人想过把它倒过来判断n是否为素数。首先,若a与n不互质,那么n为合数。所以只需要满足an-1Ξ1(mod n)即可,这个a干脆就让它等于2了。即判断2n-1Ξ1(mod n)是否成立。若不成立,那么n必定为合数。但成立时n就是素数吗?又有人找出了个数:2340Ξ1(mod 341),但是发现341是合数(11*33)。那我们能不能直接把a换成另一个数呢?答案是否定的。因为对于所有a,都存在an-1Ξ1(mod n),其中n为合数。于是这个方法就出错了。但是紧接着Miller Rabin测试对这个方法进行了改进。

  Miller Rabin的依据是,当n为素数时,x2Ξ1(mod n)的根有两个:x=1和x=n-1。这个根叫做平凡平方根(真的拗口)。因此,如果对于模n存在1的非平凡平方根,则n是个合数。

  那么Miller Rabin怎么改进的呢?

    ①选取多个基数a;

    ②寻找模n为1的非平凡平方根:令2t*u=n-1(t>=1,u为奇数),则an-1=a2t*u=(au)2t。先算出x=au mod n,再把x平方t次,每次模上n,这样我们就得到了一个长度为t+1的序列。我们希望这个序列以1结尾,并且若某一项为1,则前一项必须为1或n-1,否则n就是合数。

  这并不是简单地验证一下费马小定理。Miller Rabin会对一个数进行s次测试,其出错率低至2-s

  然后是代码(喜人的rand):

#include <cstdio>
#include <cstdlib>
#include <ctime>
using namespace std;

inline long long pow(long long a, long long b, long long p){
    long long ans = 1 % p;
    while(b){
        if(b & 1) ans = ans * a % p;
        a = a * a % p;
        b >>= 1;
    }
    return ans;
}

inline bool judge(long long n, long long a){
    long long u = 0, t = n - 1;
    while(t % 2 == 0) u++, t /= 2;
    long long x = pow(a, t, n);
    for(long long i = 1; i <= u; i++){
        long long next = x * x % n;
        if(next == 1 && x != 1 && x != n - 1) return true;
        x = next;
    }
    return x == 1 ? false : true;
}

inline bool rabin(long long n){
    if(n == 2) return true;
    if(n < 2 || n % 2 == 0) return false;
    for(long long i = 1; i <= 10; i++){
        long long a = rand() % (n - 2) + 2;
        if(judge(n, a)) return false;
    }
    return true;
}

int main(){
    srand(time(0));
    long long t, in;
    scanf("%lld", &t);
    while(t--){
        scanf("%lld", &in);
        printf("%s\n", rabin(in) ? "yes" : "no");
    }
    return 0;
}

  据说重庆OI出过一道Miller Rabin的题。

猜你喜欢

转载自www.cnblogs.com/akura/p/10708849.html