【总结】Miller-Rabin 素数测试

前置芝士:质数的基本判断方法及素数筛 link

0x01 问题描述

给定一个数 n , n ∈ Z 且 2 ≤ n ≤ 2 63 − 1 n,n \in \mathbb{Z} 且 2 \leq n \leq 2^{63}-1 n,nZ2n2631,判断 n n n 是否为质数。

时间限制: 1000 ms 1000 \text{ms} 1000ms/空间限制: 128 MB 128 \text{MB} 128MB

0x02 问题分析

考虑到 n n n 的范围,如果使用传统的方法(枚举 1 ∼ n 1 ∼ \sqrt{n} 1n 的数,判断是否能整除 n n n),时间复杂度 O ( n ) O(\sqrt{n}) O(n ),无法在规定时间内通过此题

因此,我们需要采用 log ⁡ \log log 甚至常数级别的算法来通过此题

0x03 算法讲述

引理:ex费马小定理

描述:

如果 n n n 为素数,则 ∀ a < n \forall a<n a<n,设 n − 1 = d ⋅ 2 r n-1=d\cdot 2^r n1=d2r
d d d 为奇数,则以下两个命题至少有一个成立:

  • a d ≡ 1 ( m o d n ) a^d\equiv 1\pmod n ad1(modn)
  • ∃ 0 ≤ i < r , s.t.  a d ⋅ 2 i ≡ − 1 ( m o d n ) \exists 0\le i<r,\text{s.t. }a^{d\cdot 2^i}\equiv -1\pmod n 0i<r,s.t. ad2i1(modn)

证明网上都有,由于这不是本篇的讲述重点,所以略过

它的逆命题不一定成立

Miller-Rabin 算法是这样的:

取一些数 1 ≤ a 1 , a 2 , ⋯   , a k < n 1\leq a_1,a_2,\cdots,a_k<n 1a1,a2,,ak<n,然后把它们作为定理中的 a a a 代入到上述定理中看看定理是否成立,如果都成立,那么 n n n 是素数,否则 n n n 不是素数。

这个算法将有小概率是错的,但是通过多取几个较强的 a a a,可以在 long long \text{long long} long long 范围内做到 100 % 100\% 100% 的正确率!

这里我通过在网上嫖各种前辈的经验,找到了几个可取的 a a a:

[ 2 , 3 , 5 , 7 , 11 , 13 , 17 , 37 , 24251 ] [2,3,5,7,11,13,17,37,24251] [2,3,5,7,11,13,17,37,24251]

当然,如果你不怕被卡常,为了保证正确率,在上述基础上,可以取前 10 ∼ 15 10∼15 1015大的质数作为 a a a

0x04 算法实现

有一些特判,没有详细讲,在代码中有

需要用到快速幂和快速乘

#include<cstdio>
#include<iostream>
#include<algorithm>
#define ll long long 
const ll prime[]={2,3,5,7,11,13,17,37,24251};
ll mul(ll x,ll y,ll p){
    ll res=0;
    while(y){
        if(y&1){
        	res=(res+x)%p;
		}
        x=(x+x)%p;
        y>>=1;
    }
    return res;
}
ll qpow(ll x,ll y,ll p){
    ll res=1;
    while(y){
        if(y&1){
        	res=mul(res,x,p);
		}
        x=mul(x,x,p);
        y>>=1;
    }
    return res;
}
bool Miller_Rabin(ll n,ll a){
    ll d=n-1,r=0;
    while(!(d&1)){
        d>>=1,r++;
	}
    ll z=qpow(a,d,n);
    if(z==1){
    	return true;
	} 
    for(int i=0;i<r;i++){
        if(z==n-1){
        	return true;
		}
        z=mul(z,z,n);
    }
    return false;
}
bool isPrime(ll n){
    if(n<=1)return false;
    for(int i=0;i<9;i++){
        if(n==prime[i]){
        	return true;
		}
        if(n%prime[i]==0){
        	return false;
		}
        if(!Miller_Rabin(n,prime[i])){
        	return false;
		}
    }
    return true;
}
int main(){
    int T;
    scanf("%d",&T);
    while(T--){
        ll n;
        scanf("%lld",&n);
        if(isPrime(n)){
        	puts("YES");
		}
        else{
        	puts("NO");
		}
    }
    return 0;
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值