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; }