用途:快速判断一个数是否是素数
费马小定理:对于素数p与任意与p互素的整数a满足a(p-1) ≡1(mod p)
若整数p为素数,必然满足费马小定理,故随机选取一些a快速幂判断是否成立即可,若费马小定理公式成立则认为p有极大概率为素数。
一些数具有巧妙地性质,对于一些特殊的a满足费马小定理公式,却不是素数,比如伪素数341=11*31,但用a=2不能正确探测,更有一些绝对伪素数如561=3*11*17,无论用什么样的a,只要a和p互素都无法成功。故引用确定性的二次探测算法。
对于a在满足费马小定理公式的前提下,不断运用引理进行二次探测。即:若a(p-1) ≡1(mod p),若(p-1)为偶数,则应有(a (p-1)/2)2 ≡ ± 1(mod p),若为+1,继续看(p-1)/2是否为偶数以此类推。
这就是Fermat素性测试。
人们自然会想,如果考虑了所有小于n的底数a,出错的概率是否就可以降到0呢?没想到的是,居然就有这样的合数,它可以通过所有a的测试。Carmichael第一个发现这样极端的伪素数,他把它们称作Carmichael数。你一定会以为这样的数一定很大。错。第一个Carmichael数小得惊人,仅仅是一个三位数,561。前10亿个自然数中Carmichael数也有600个之多。Carmichael数的存在说明,我们还需要继续加强素性判断的算法。
Miller和Rabin两个人的工作让Fermat素性测试迈出了革命性的一步,建立了传说中的Miller-Rabin素性测试算法。新的测试基于下面的定理:
引理:若p为奇素数,方程x^2≡1(mod p)的解只有±1
如果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能被p整除(此时x=1)或x+1能被p整除(此时x=p-1)。
Miller-Rabin素性测试的方法:
不断地提取指数n-1中的因子2,把n-1表示成d*2^r(其中d是一个奇数)。那么我们需要计算的东西就变成了a的d*2^r次方除以n的余数。于是,a^(d * 2^(r-1))要么等于1,要么等于n-1。如果a^(d * 2^(r-1))等于1,定理继续适用于a^(d * 2^(r-2)),这样不断开方开下去,直到对于某个i满足a^(d * 2^i) mod n = n-1或者最后指数中的2用完了得到的a^d mod n=1或n-1。这样,Fermat小定理加强为如下形式:
尽可能提取因子2,把n-1表示成d*2^r,如果n是一个素数,那么或者a^d mod n=1,或者存在某个i使得a^(d*2^i) mod n=n-1 ( 0<=i
代码
// by spli
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
#include<cmath>
#include<map>
#define LL long long
using namespace std;
const int t=20;
int x;
int p,q;
map<LL,LL,int>m;
LL random(LL a){//随机一个数
return ((double)rand()/RAND_MAX*a+0.5);
}
LL exp(LL a,LL b,LL p){//快速幂
LL res=1;
while(b){
if(b&1) (res*=a)%=p;
b>>=1;
(a*=a)%=p;
}
return res;
}
LL mul(LL a,LL b,LL p){//快速乘法
LL res=0;
while(b){
if(b&1) (res+=a)%=p,b--;//
b>>=1;
(a+=a)%=p;
}
return res;
}
bool judge(LL a,LL n){
LL tmp=n-1;
int j=0;
while(tmp%2==0){
tmp/=2;
j++;
}
LL b=exp(a,tmp,n);
if(b==1||b==n-1) return 1;
while(j--){
b=mul(b,b,n);
if(b==n-1) return 1;
}
return 0;
}
bool miller_rabin(LL n){
if(n==2) return 1;
if(n<2||n%2==0) return 0;
for(int i=1;i<=t;++i){
LL a=random(n-2)+1;
if(!judge(a,n)) return 0;
}
return 1;
}
int main(){
scanf("%d%d",&p,&q);
while(q--){
scanf("%d",&x);
if(miller_rabin(x))
cout<<"Yes"<<endl;
else cout<<"No"<<endl;
}
return 0;
}