Miller–Rabin 素性測試

jxy2012發表於2024-04-14

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;
}

相關文章