Miller-Rabin素性测试

可以去看本文的大部分来源:数论部分第一节:素数与素性测试

在判断一个数是否为质数时,我们通常选择根号 n 的试除法来进行判断

但这是在询问比较少/数据比较小的前提下的

Miller-Rabin 素性测试为我们提供了一种 logn 的做法

 

我们知道 Fermat 小定理 a^(p - 1) mod p = 1 是对于 p 为质数且 a,p 互质的情况成立的

那么其逆命题成立吗?

过去人们是这样认为的

但由于伪素数的发现,人们终于认识到它是错误的

不过这样的数还是占据少数的

所以我们至少可以用它来先验证出一部分数,尽管这个集合中有伪素数

可以通过多取一些 a 的值来降低错误概率

以上是Fermat素性测试。

 

那么我们自然会这样想,如果我们考虑了所有的小于 n 的底数 a ,是否就足够区分素数和其他数呢?

很可惜,这样的合数依然存在

他们被称为“Carmichael数

你一定会认为这样极端的伪素数一定很大

然而第一个 Carmichael数仅仅是一个三位数:561

而且前 10 亿个自然数中 Carmichael数有600个之多

Carmichael数的存在说明,我们还需要继续加强素性判断的算法。

 

Miller和Rabin两个人的工作让Fermat素性测试迈出了革命性的一步,建立了传说中的Miller-Rabin素性测试算法

新的测试基于下面的定理:

  如果 p 是素数,x 是小于 p 的整数,且 x^2 mod p = 1 ,那么要么 x = 1 ,要么 x = p - 1,这是显然的,因为 x^2 mod p = 1 相当于 p 能整除 x^2 - 1,也即p能整除 (x + 1)(x - 1) 。由于 p 是素数,那么只可能是 x - 1 = 0(mod p) (此时 x = 1 )或 x + 1 = 0(mod p) 整除(此时 x = p - 1 )。

下面用 341 来演示一下上面的定理如何运用到 Fermat定理上:

   2^340 mod 341 = 1  => (2^170)^2 mod 341 = 1

  若 341 为素数,则上面方程的解可能是 1 或 340,若等于 1,我们继续上面的变形得:

  (2^85)^2 mod 341 = 1

  然而计算得 2^85 mod 341 = 32

  那么 341 不是素数

这就是 Miller-Rabin 素性测试的方法,通过不断提取指数 p - 1中的因子 2 ,将方程化为 x^2 mod p = 1 的形式, 根据解 只能为 1 或 p - 1 ,若解为 1,继续重复上述过程,解为 p - 1,停止,这个数目前为止我们认为它可能是一个素数,解为其他数,则这个数不是素数

时间复杂度 O(logn)

Miller-Rabin素性测试同样为不确定算法,我们把可以通过以a为底的Miller-Rabin测试的合数称作以a为底的强伪素数(strong pseudoprime)。第一个以2为底的强伪素数为2047。第一个以2和3为底的强伪素数则大到1 373 653。

以上是 Miller-Rabin 素性测试算法。

 

通常我们使用该算法时,都是要来判断 long long 级别的数字,由于可能在快速幂里乘法的步骤乘爆了,需要快速乘, O(logn),写起来跟快速幂差不多

附上一道例题:hihocoder 1287 数论一·Miller-Rabin质数测试

 

代码:

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cctype>
#include<cstdio>
using namespace std;
 
typedef long long ll;
 
int m, n;
ll ps[12] = {2ll, 3ll, 5ll, 7ll, 11ll, 13ll, 17ll, 19ll, 23ll, 29ll, 31ll, 37ll};
 
inline ll turtle_mul(ll a, ll b, ll mod) {
    ll ans = 0ll;
    while(b) {
        if(b & 1) ans = (ans + a) % mod;
        a = (a << 1) % mod;
        b >>= 1;
    }
    return ans;
}
inline ll fast_pow(ll low, ll up, ll mod) {
    ll ans = 1ll;
    low %= mod;
    while(up) {
        if(up & 1ll) ans = turtle_mul(ans, low, mod);
        up >>= 1;
        low = turtle_mul(low, low, mod);
    }
    return ans;
}
inline bool dvd_chk(ll bot, ll top, ll mod) {
	register ll tmp;
	while (!(top & 1)) {
		tmp = fast_pow(bot, top, mod);
		if (tmp == 1ll) top >>= 1;
		else if (tmp == mod - 1ll) return true;
		else return false;
	}
	return true;
}
inline bool Test(ll a) {
    if(a <= 1) return false;
    if(a == 2ll) return true;
    if(!(a & 1ll)) return false;
    for(int i = 0; i < 12; ++i) {
        if(ps[i] == a)
            return true;   
        if(fast_pow(ps[i], a - 1ll, a) != 1ll)
            return false;  
        else if(!dvd_chk(ps[i], a - 1ll, a))
            return false;
    }
    return true;
}
 
int main() {
    scanf("%d", &m);
    ll num;
    while(m--) {
        scanf("%lld", &num);
        if(Test(num)) puts("Yes");
        else puts("No");
    }
    return 0;
}

  

 

posted @ 2018-07-04 10:34  EvalonXing  阅读(320)  评论(0编辑  收藏  举报