miller_rabin学习笔记 数论

首先介绍一下miller_rabin算法。
miller_rabin是一种素性测试算法,用来判断一个大数是否是一个质数。
miller_rabin是一种随机算法,它有一定概率出错,设测试次数为 s s ,那么出错的概率是 4s 4 − s ,至于为什么我也不会证明。我觉得它的复杂度是 O(slog2n) O ( s l o g 2 n ) ,因为你要进行 s s 次,每次要进行一次快速幂,每次快速幂要 logn l o g n 次快速乘,每次快速乘又是 logn l o g n 的,所以这一部分是 log2n l o g 2 n 的,另一部分是把 n n 分解成 u2k u ∗ 2 k ,复杂度是 logn l o g n ,所以k是 logn l o g n 量级的,对于 k k ,每次要快速乘,所以这一部分的复杂度也是 log2n l o g 2 n 的,于是总复杂度 O(mlog2n) O ( m l o g 2 n )
下面开始介绍miller_rabin的做法。
首先我们知道,根据费马小定理,如果 p p 为质数且 a a p p 互质,那么有 ap1=1(mod p) a p − 1 = 1 ( m o d   p ) ,如果我们让 a<n a < n ,那么只需要满足 p p 为质数即可。但是反过来,满足 ap1=1(mod p) a p − 1 = 1 ( m o d   p ) 的p不一定是质数。但是幸运的是,对于大多数的 p p ,费马小定理的逆定理是对的,但是为了让我们得到正确是结果,我们需要提高正确率。所以接下来我们需要二次探测。
n n 为质数,并且 x2=1(mod n) x 2 = 1 ( m o d   n ) ,那么 x=1 x = 1 x=n1 x = n − 1 ,因为 x2=1 x 2 = 1 ,则 x=1 x = 1 x=1 x = − 1 ,而在模意义下这个 1 − 1 就成了 n1 n − 1 。我们来证明一下为什么在 x2=1(mod p) x 2 = 1 ( m o d   p ) ,且 x1(mod p) x ≠ 1 ( m o d   p ) ,且 xp1(mod p) x ≠ p − 1 ( m o d   p ) p p 不是质数。

x2=1(mod p) x 2 = 1 ( m o d   p )

x21=0(mod p) x 2 − 1 = 0 ( m o d   p )

(x1)(x+1)=0(mod p) ( x − 1 ) ( x + 1 ) = 0 ( m o d   p )

所以有 p|(x1) p | ( x − 1 ) p|(x+1) p | ( x + 1 ) p|(x+1)(x1) p | ( x + 1 ) ( x − 1 )
因为 xp1(mod p) x ≠ p − 1 ( m o d   p )
所以 (x+1)%p0 ( x + 1 ) % p ≠ 0 , p p 不整除 x+1 x + 1
因为 x1(mod p) x ≠ 1 ( m o d   p )
所以 (x1)%p0 ( x − 1 ) % p ≠ 0 , p p 不整除 x1 x − 1
所以只能是 p|(x+1)(x1) p | ( x + 1 ) ( x − 1 )
由此我们可以发现 x+1 x + 1 x1 x − 1 都不含 p p 的所有因子,而它们的乘积却含有的 p p 的全套因子,那么在相乘时 x+1 x + 1 x1 x − 1 分别提供了一个非 1 1 p p 的因子,才能使乘积是 p p 的倍数(因为 p|(x+1)(x1) p | ( x + 1 ) ( x − 1 ) 嘛),当然这两个因子可能相同。
那么,我们可以证明出 p p 可以表示为两个非 1 1 p p 的数的乘积,那么就证明了 p p 是合数而不是质数了。
我们可以根据这个性质进行二次探测。
如果我们设要测试的数为 n n ,若 n n 2 2 n n 是偶数,那么我们可以直接判断 n n 的奇偶,否则令 p=n1 p = n − 1 ,将 p p 分解成 u2k u ∗ 2 k ,枚举 k k 来不断进行二次探测。则那么我们随机一个底数 x x ,快速幂求出 xu(mod n) x u ( m o d   n ) ,然后不断的 xx mod p x ∗ x   m o d   p 来进行二次探测。
至于为什么在代码中这么做是对的,我看到以了个很好的解释,在这里分享一下。
ap1=1(mod p) a p − 1 = 1 ( m o d   p )

au2k=1(mod p) a u ∗ 2 k = 1 ( m o d   p )

(au2k1)(au2k1)=1(mod p) ( a u ∗ 2 k − 1 ) ∗ ( a u ∗ 2 k − 1 ) = 1 ( m o d   p )

那么我们把 au2k1 a u ∗ 2 k − 1 看作 x x ,那么就是在当前 au2k=1(mod p) a u ∗ 2 k = 1 ( m o d   p ) 的时候验证 au2k1(mod p) a u ∗ 2 k − 1 ( m o d   p ) 是否是 p1 p − 1 或者 1 1 即可。
另外最后别忘了用费马小定理来判断一下,代码中的 x x 其实的 xp1(mod p) x p − 1 ( m o d   p ) 之后的结果,所以只需要看最后的 x x 是否是 1 1 即可。
最后是代码:

#include 
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,否则可以判断n不是素数
            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
(都是模板题)
最后:特别鸣谢wpc、was_n、DT_Kang和liuzhangfeiabc帮忙给出的一些证明

你可能感兴趣的:(数论,学习笔记)