一般情况下判断一个数是不是质数时间复杂度是 O ( n ) O(\sqrt{n}) O(n),用 M i l l e r − R a b i n Miller-Rabin Miller−Rabin 素数测试法可以降低时间复杂度。
前置知识
1. 1. 1. 费马小定理 : : : 当 p p p 为素数,且 g c d ( a , p ) = 1 gcd(a,p) = 1 gcd(a,p)=1,有 a p − 1 ≡ 1 ( m o d p ) a^{p-1} \equiv 1\pmod{p} ap−1≡1(modp),这是 p p p 为素数的必要不充分条件。
2. 2. 2. 二次探测定理 : : : 当 p p p 为素数,且 0 < x < p 0 < x < p 0<x<p,则方程 x ≡ 1 ( m o d p ) x\equiv1\pmod{p} x≡1(modp) 的解为 x = 1 x=1 x=1 或 x = p − 1 x=p-1 x=p−1。证明把 1 1 1 移项即可。
M i l l e r − R a b i n Miller-Rabin Miller−Rabin
除 2 2 2 以外,所有的 p − 1 p-1 p−1 都可以表示为 2 t × u 2^t \times u 2t×u 的形式,则 a p − 1 a^{p-1} ap−1 = a 2 t × u a^{2^t \times u} a2t×u = a u 2 t a^{u^{2^t}} au2t。所以我们构造一个 b b b 数组,令 b [ 0 ] = a u b[0] = a^u b[0]=au,只需要每次都平方, b [ i ] = b [ i − 1 ] 2 b[i] = b[i-1]^2 b[i]=b[i−1]2,则 b [ t ] = a u 2 t b[t] = a^{u^{2^t}} b[t]=au2t。构造出 b [ i ] b[i] b[i] 之后根据二次探测定理进行判断。如果 b [ i ] = 1 b[i] = 1 b[i]=1,则 b [ i − 1 ] = 1 b[i-1] = 1 b[i−1]=1 或 b [ i − 1 ] = p − 1 b[i-1] = p-1 b[i−1]=p−1,如果不满足以上的条件,那么 p p p 一定是合数,如果满足了,那么 p p p 很有可能是质数。
由于自乘 k k k 次,最后会得到 a p − 1 a^{p-1} ap−1,如果模 p p p 不等于 1 1 1,那么不满足费马小定理,也是合数。
用 2 , 3 , 5 , 7 , 11 , 13 , 17 2,3,5,7,11,13,17 2,3,5,7,11,13,17 这几个数,在 O I OI OI 范围内出错的概率为 0 0 0。
注意会爆 l o n g l o n g long long longlong,可以用快速乘或者是 __ i n t 128 int128 int128。
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#define re register
#define int __int128
#define drep(a,b,c) for(re int a(b) ; a>=(c) ; --a)
#define rep(a,b,c) for(re int a(b) ; a<=(c) ; ++a)
using namespace std;
inline int read(){
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch == '-') f=-1 ; ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
inline void print(int x){
if(x < 0) putchar('-'),x = -x;
if(x >= 10) print(x / 10);
putchar(x % 10 + '0');
}
const int M = 10;
const int mod = 998244353;
const int prime[] = {0,2,3,5,7,11,13,17};
int T,n;
inline int ksm(int x,int y,int p){
int res = 1;
while(y){
if(y&1) res = res*x%p;
x = x*x%p;
y>>=1;
}
return res%p;
}
inline void work(){
int n = read();
if(n == 1) { printf("NO\n"); return; }
int t = n-1,k = 0;
while(!(t&1)) k++,t>>=1;
rep(i,1,7){
if(n == prime[i]) { printf("YES\n"); return; }
int base = ksm(prime[i],t,n),now = base;
rep(j,0,k){
now = base*base%n;
if(now == 1 && base^1 && base^(n-1)) { printf("NO\n"); return; }
base = now;
}
if(base^1) { printf("NO\n"); return; }
}
printf("YES\n");
return;
}
signed main(){
T = read();
while(T--) work();
return 0;
}