看一个数是否为质数,我们通常会用那个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*31)。那我们能不能直接把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的题。
总结:
要点1.Miller Rabin是将费马小定理倒转过来,验证n是否存在非平凡平方根.
要点2.平凡平方根:x2Ξ1(mod n),x=1或x=n-1
要点3.对于基数a的判断基于倍增的思想.