首先介绍一下miller_rabin算法。
miller_rabin是一种素性测试算法,用来判断一个大数是否是一个质数。
miller_rabin是一种随机算法,它有一定概率出错,设测试次数为
,那么出错的概率是
,至于为什么我也不会证明。我觉得它的复杂度是
,因为你要进行
次,每次要进行一次快速幂,每次快速幂要
次快速乘,每次快速乘又是
的,所以这一部分是
的,另一部分是把
分解成
,复杂度是
,所以k是
量级的,对于
,每次要快速乘,所以这一部分的复杂度也是
的,于是总复杂度
。
下面开始介绍miller_rabin的做法。
首先我们知道,根据费马小定理,如果
为质数且
与
互质,那么有
,如果我们让
,那么只需要满足
为质数即可。但是反过来,满足
的p不一定是质数。但是幸运的是,对于大多数的
,费马小定理的逆定理是对的,但是为了让我们得到正确是结果,我们需要提高正确率。所以接下来我们需要二测探测。
若
为质数,并且
,那么
或
,因为
,则
或
,而在模意义下这个
就成了
。我们来证明一下为什么在
,且
,且
时
不是质数。
所以有 或 或
因为
所以 , 不整除
因为
所以 , 不整除
所以只能是
由此我们可以发现 和 都不含 的所有因子,而它们的乘积却含有的 的全套因子,那么在相乘时 和 分别提供了一个非 非 的因子,才能使乘积是 的倍数(因为 嘛),当然这两个因子可能相同。
那么,我们可以证明出 可以表示为两个两个非 非 的数的乘积,那么就证明了 是合数而不是质数了。
我们可以根据这个性质进行二次探测。
如果我们设要测试的数为 ,若 是 或 是偶数,那么我们可以直接判断 的奇偶,否则令 ,将 分解成 ,枚举 来不断进行二次探测。则那么我们随机一个底数 ,快速幂求出 ,然后不断的 来进行二次探测。
至于为什么在代码中这么做是对的,我看到以了个很好的解释,在这里分享一下。
那么我们把 看作 ,那么就是在当前 的时候验证 是否是 或者 即可。
另外最后别忘了用费马小定理来判断一下,代码中的 其实的 之后的结果,所以只需要看最后的 是否是 即可。
最后是代码:
#include <bits/stdc++.h>
using namespace std;
int q,m,s=5;//s为测试次数(选了几次底数a)
inline long long ksc(long long x,long long y,long long mod)//快速乘
{
long long res=0;//注意赋初值
// x%=mod;
while(y)
{
if(y&1)
res=(res+x)%mod;
x=(x<<1)%mod;
y>>=1;
}
return res;
}
inline long long ksm(long long x,long long y,long long mod)
{
long long res=1;//注意设为1,不是0
// x%=mod;
while(y)
{
if(y&1)
res=ksc(res,x,mod);
x=ksc(x,x,mod);
y>>=1;
}
return res;
}
inline int miller_rabin(long long n)
{
if(n==2||n==3||n==5||n==7||n==11)
return 1;
if(n<2||!(n%2)||!(n%3)||!(n%5)||!(n%7)||!(n%11))
return 0;
long long x,pre,u;//pre为上次的结果
int k=0;//k为n分解成了2的多少次方
//最终n被分解为u*2^k
u=n-1;//求x^u%n;
while(!(u&1))//u为偶数则右移,否则就停
{
++k;
u>>=1;
}
srand(time(0)+19260817);
for(int i=1;i<=s;++i)
{
x=rand()%(n-2)+2;//生成一个[2,n)的随机底数
x=ksm(x,u,n);//先求出x^u mod n
pre=x;
for(int j=1;j<=k;++j)//把移位减掉的量补上,并在这地方进行二次探测
{
x=ksc(x,x,n);
if(x==1&&pre!=1&&pre!=n-1)//二次探测定理,这里如果x = 1则pre 必须等于 1,或则 n-1否则可以判断不是素数
return 0;
pre=x;
}
if(x!=1)//费马小定理
return 0;
}
return 1;
}
int main()
{
scanf("%d%d",&q,&m);
for(int i=1;i<=m;++i)
{
int x,pd;
scanf("%d",&x);
pd=miller_rabin(x);
if(pd==1)
printf("Yes\n");
else
printf("No\n");
}
return 0;
}
最后发一个题单:
洛谷3383
poj1811
HDU2138
(都是模板题)