Miller–Rabin 素性測試(Miller–Rabin primality test)是進階的素數判定方法。它是由 Miller 和 Rabin 二人根據費馬小定理的逆定理(費馬測試)最佳化得到的。因為和許多類似演算法一樣,它是使用偽素數的機率性測試,我們必須使用慢得多的確定性演算法來保證素性。然而,實際上沒有已知的數字透過了高階機率性測試(例如 Miller–Rabin)但實際上卻是複合的。因此我們可以放心使用。
在不考慮乘法的複雜度時,對數 \(n\) 進行 \(k\) 輪測試的時間複雜度是 \(O(k \log n)\)。Miller-Rabbin 素性測試常用於對高精度數進行測試,此時時間複雜度是 \(O(k \log^3n)\),利用 FFT 等技術可以最佳化到 \(O(k \log^2n \log \log n \log \log \log n)\)。
二次探測定理
如果 \(p\) 是奇素數,則 \(x^2 \equiv 1 \pmod p\) 的解為 \(x \equiv 1 \pmod p\) 或者 \(x \equiv p - 1 \pmod p\)。
要證明該定理,只需將上面的方程移項,再使用平方差公式,得到 \((x+1)(x-1) \equiv 0 \bmod p\),即可得出上面的結論。
實現
根據卡邁克爾數的性質,可知其一定不是 \(p^e\)。
不妨將費馬小定理和二次探測定理結合起來使用:
將 \(a^{n-1} \equiv 1 \pmod n\) 中的指數 \(n−1\) 分解為 \(n−1=u \times 2^t\),在每輪測試中對隨機出來的 \(a\) 先求出 \(v = a^{u} \bmod n\),之後對這個值執行最多 \(t\) 次平方操作,若發現非平凡平方根時即可判斷出其不是素數,否則再使用 Fermat 素性測試判斷。
還有一些實現上的小細節:
- 對於一輪測試,如果某一時刻 \(a^{u \times 2^s} \equiv n-1 \pmod n\),則之後的平方操作全都會得到 \(1\),則可以直接透過本輪測試。
- 如果找出了一個非平凡平方根 \(a^{u \times 2^s} \not\equiv n-1 \pmod n\),則之後的平方操作全都會得到 \(1\)。可以選擇直接返回
false
,也可以放到 \(t\) 次平方操作後再返回false
。
這樣得到了較正確的 Miller Rabin。
程式碼:
bool MillerRabin(int n) {
if (n == 2) return true;
if (n <= 1 || n % 2 == 0) return false;
ll base[7] = {2, 325, 9375, 28178, 450775, 9780504, 1795265022};
ll u = n - 1, k = 0;
while (u % 2 == 0) u /= 2, k++;
for (auto x : base) {
if (x % n == 0) continue;
ll v = powmod(x, u, n);
if (v == 1 || v == n - 1) continue;
for (int j = 1; j <= k; j++) {
ll last = v;
v = (__int128)v * v % n;
if (v == 1) {
if (last != n - 1) return false;
break;
}
}
if (v != 1) return false;
}
return true;
}
另外,假設 廣義 Riemann 猜想(generalized Riemann hypothesis, GRH)成立,則對數 \(n\) 最多隻需要測試 \([2, \min\{n-2, \lfloor 2\ln^2 n \rfloor\}]\) 中的全部整數即可 確定 數 \(n\) 的素性。
而在 OI 範圍內,通常都是對 \([1, 2^{64})\) 範圍內的數進行素性檢驗。對於 \([1, 2^{32})\) 範圍內的數,選取 \(\{2, 7, 61\}\) 三個數作為基底進行 Miller–Rabin 素性檢驗就可以確定素性;對於 \([1, 2^{64})\) 範圍內的數,選取 \(\{2, 325, 9375, 28178, 450775, 9780504, 1795265022\}\) 七個數作為基底進行 Miller–Rabin 素性檢驗就可以確定素性。
也可以選取 \(\{2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37\}\)(即前 \(12\) 個素數)檢驗 \([1, 2^{64})\) 範圍內的素數。
注意如果要使用上面的數列中的數 \(a\) 作為基底判斷 \(n\) 的素性:
- 所有的數都要取一遍,不能只選小於 \(n\) 的;
- 把 \(a\) 換成 \(a \bmod n\);
- 如果 \(a \equiv 0 \pmod n\),則直接透過該輪測試。
模板題:loj143
程式碼:
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll mod = 1e9 + 7;
const int N = 200005;
const int INF = 0x3f3f3f3f;
ll powmod(__int128 x, ll y, ll mod) {
ll res = 1;
while (y) {
if (y & 1) res = res * x % mod;
y >>= 1;
x = x * x % mod;
}
return res;
}
bool MillerRabin(ll n) {
if (n == 2) return true;
if (n <= 1 || n % 2 == 0) return false;
ll base[7] = {2, 325, 9375, 28178, 450775, 9780504, 1795265022};
ll u = n - 1, k = 0;
while (u % 2 == 0) u /= 2, k++;
for (auto x : base) {
if (x % n == 0) continue;
ll v = powmod(x, u, n);
if (v == 1 || v == n - 1) continue;
for (int j = 1; j <= k; j++) {
ll last = v;
v = (__int128)v * v % n;
if (v == 1) {
if (last != n - 1) return false;
break;
}
}
if (v != 1) return false;
}
return true;
}
int main() {
ll n;
while (scanf("%lld", &n) != EOF) {
if (Mr(n))
printf("Y\n");
else
printf("N\n");
}
return 0;
}