前置芝士:质数的基本判断方法及素数筛 link
0x01 问题描述
给定一个数 n , n ∈ Z 且 2 ≤ n ≤ 2 63 − 1 n,n \in \mathbb{Z} 且 2 \leq n \leq 2^{63}-1 n,n∈Z且2≤n≤263−1,判断 n n n 是否为质数。
时间限制: 1000 ms 1000 \text{ms} 1000ms/空间限制: 128 MB 128 \text{MB} 128MB
0x02 问题分析
考虑到 n n n 的范围,如果使用传统的方法(枚举 1 ∼ n 1 ∼ \sqrt{n} 1∼n的数,判断是否能整除 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
n−1=d⋅2r
,
d
d
d 为奇数,则以下两个命题至少有一个成立:
- a d ≡ 1 ( m o d n ) a^d\equiv 1\pmod n ad≡1(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 ∃0≤i<r,s.t. ad⋅2i≡−1(modn)
证明网上都有,由于这不是本篇的讲述重点,所以略过
它的逆命题不一定成立
Miller-Rabin 算法是这样的:
取一些数 1 ≤ a 1 , a 2 , ⋯ , a k < n 1\leq a_1,a_2,\cdots,a_k<n 1≤a1,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 10∼15大的质数作为 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;
}