【學習筆記】Miller-Rabin素性測試與Pollard-Rho大數分解

【BZOJ 3667】
第一行:CAS,代表數據組數(不大於350),以下CAS行,每行一個數字,保證在64位長整形範圍內,並且沒有負數。你需要對於
每個數字:第一,檢驗是否是質數,是質數就輸出Prime
第二,如果不是質數,輸出它最大的質因子是哪個。


檢驗一個數是否是質數的樸素方法是O(n)O(\sqrt{n})的,並不能滿足這道題的需求。
其實直到現在,人們對一個數的素性判定依舊沒有正確率爲100%的方法。但素數簡直是太重要了,我們生活中方方面面都要用到它(如著名的RSA加密算法)。所以,爲了應付日常的生產需求,人們發明了一個能較快判斷素數的算法。雖然正確率達不到100%,但如果我們的參數選取適當,就可以做到十分的準確。這就是今天要介紹的Miller-Rabin素性測試。


這個算法的準確性保證來自於這兩個定理:
pa滿ap11mod pa21mod pa[1,p1]a=1p1費馬小定理:對於任意素數p與非負整數a,滿足:\newline a^{p-1} \equiv1 \quad mod \space p \newline 二次探測定理:若a^2 \equiv1 \quad mod \ p且a\in [1,p-1],則a=1或p-1
第二個定理的證明比較簡單:
a21mod ppa21,p(a+1)(a1)a[1,p1]pa=1p1 \because a^2 \equiv1 \quad mod \ p \\ \therefore p|a^2-1,即p|(a+1)(a-1) \\ \because a\in[1,p-1]且p爲質數 \\ \therefore a=1或p-1


終於可以開始正題了。
但是在開始前,還是得先講一個東西。

·Miller-Rabin素性測試的前身-費馬測試。
還記得費馬小定理嗎?ap11mod p a^{p-1} \equiv1 \quad mod \space p
雖然滿足上式的數並不一定是一個素數,但在很多情況下是成立的。所以,我們可以隨便選一個aa,然後代入上式驗證即可。


但可惜的是,上面的方法的容錯率還達不到我們的需求。怎麼辦呢?,接下來,該快用你無敵的白金之星想想辦法啊正式介紹Miller-Rabin素性測試了。

首先我們對偶數特判(反正它絕對不是質數)。對於待測數nn,我們隨機選擇一個底a[1,n1]a \in [1,n-1],再嘗試把nn寫成形如下形式
ar2s\Large a^{r*2^s}
寫成這樣有什麼用呢?我們先算a2ra^{2r},然後嘗試用費馬小定理。如果此時它通過了本次測試,根據二次探測定理,此時ara^r一定爲1或n1n-1。如果不是,則nn一定不是質數。否則,我們將這個東西再平方,重複上述過程,直到這s次平方全部用完,最後再判斷是否符合費馬小定理即可。
實踐中,選擇十個隨機數進行判定,其準確度已經相當高了。如果還不滿意,則可以嘗試使用隨機的素數。
這個算法的時間複雜度僅爲O(log2n)O(log^2n),已經可以應付大部分情況了。
現將代碼貼在下面:

inline bool check(ll a, ll n, ll r, ll s)
{
    ll ans = ksm(a,r,n), p = ans;
    for(int i = 1; i <= s; i++)
    {
        ans = mul(ans, ans, n);
        if(ans == 1 && p != 1 && p != n - 1) return 1;
        p = ans;
    }
    if(ans != 1) return 1;
    return 0;
}
inline bool miller_rabbin(ll n)
{
    if(n <= 1) return 0;
    if(n == 2) return 1;
    if(n % 2 == 0) return 0;
    ll r = n - 1, s = 0;
    while(r % 2 == 0) r >>= 1, ++s;
    for(int i = 0; i < 10; i++)
        if(check(rand() % (n-1) + 1, n, r, s))
            return 0;
    return 1;
}

講了這麼多,最開頭那個題的第一問,不就是一道板題嗎?
好了接下來我們討論如何分解出最大的那個質因數。

根據唯一分解定理,一個數只有一種分解方法。又由於素數的定義,我們可以得出這樣一個方法:先隨便拆一個因數出來,然後對這兩個數分別分解。在分解過程中記錄最大質數即可。
那怎麼做到“隨便拆一個因數出來”呢?這個任務就交給Pollard-Rho算法吧。
這也是個隨機算法。信仰隨機神教吧qwq。每次隨機生成一個數x1x_1,嘗試去構造另一個數x2x_2,使gcd(n,x1x2)>1gcd(n,|x_1-x_2|)>1,然後將gcd(n,x1x2)gcd(n,|x_1-x_2|)作爲nn的一個因子,遞歸地處理這個問題。
那麼這個{xi}\{x_i\}到底是怎麼生成的呢?這個算法的發明人提出按照下列公式生成這個數列,直到出現重複元素爲止。
xi=xi12+ccx_i=x_{i-1}^2+c,c爲隨機生成的一個數。
接下來遇到了一個問題:如果找遍這個數列,都不滿足gcd(n,xix2)>1gcd(n,|x_i-x_2|)>1怎麼辦?
換一個c和x1x_1再試一下即可。
……
我這麼非,一直都找不出來那我不涼了?


Pollard-Rho的效率保證:生日悖論
問一個問題:在一個班中,隨機選k(k2k ≥2)個人,他們的生日相同的概率。
不難列出式子:
ans=1i=1k365i+1365ans=1-\prod_{i=1}^k\frac{365-i+1}{365}
經計算髮現,當k23k≥23時,ansans超過了50%;當k爲59時,ans接近100%。然而n多年的讀書經歷告訴我們並非如此。
這個東西告訴我們:如果我們採取某種組合方式,從一個樣本空間中隨機取樣,抽中答案的概率比單次抽取的概率高
再看回上面的算法流程。我們從“尋找xx使gcd(x,n)>1gcd(x,n)>1”改爲“尋找x1,x2x_1,x_2使gcd(x1x2,n)>1gcd(|x_1-x_2|,n)>1”。根據上面的結論,這種方式將降低Pollard-Rho算法的重選次數,從而將時間開銷控制在可以接受的範圍內。
(網上的很多博客中寫的複雜度是O(n14)O(n^{\frac{1}{4}})。)

inline ll pollard_rho(ll n, ll c)
{
    ll k = 2, x = rand() % n, y = x, p = 1;
    for(ll i = 1; p == 1; i++)
    {
        x = (mul(x,x,n) + c) % n;
        p = y > x ? y - x : x - y;
        p = gcd(n, p);
        if(i == k) y = x, k += k;
    }
    return p;
}
void solve(ll n)
{
    if(n == 1) return;
    if(miller_rabbin(n)) {ans = max(ans, n); return;}
    ll t = n;
    while(t == n) t = pollard_rho(n, rand() % (n - 1) + 1);
    solve(t), solve(n / t);
}

至此,我們便完美地解決了這個問題,現將完整代碼貼在下面:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int mn = 100005;
ll ans;
ll gcd(ll a, ll b) {return b ? gcd(b, a % b) : a;}
inline ll mul(ll a, ll b, ll p)
{
    a = (a % p + p) % p, b = (b % p + p) % p;
    return (((a * b) - (ll)((long double)a * b / p) * p) % p + p) % p;
}
inline ll ksm(ll a, ll b, ll p)
{
    ll ret = 1;
    while(b)
    {
        if(b & 1) ret = mul(ret, a, p);
        a = mul(a, a, p), b >>= 1;
    }
    return ret;
}
inline bool check(ll a, ll n, ll r, ll s)
{
    ll ans = ksm(a,r,n), p = ans;
    for(int i = 1; i <= s; i++)
    {
        ans = mul(ans, ans, n);
        if(ans == 1 && p != 1 && p != n - 1) return 1;
        p = ans;
    }
    if(ans != 1) return 1;
    return 0;
}
inline bool miller_rabbin(ll n)
{
    if(n <= 1) return 0;
    if(n == 2) return 1;
    if(n % 2 == 0) return 0;
    ll r = n - 1, s = 0;
    while(r % 2 == 0) r >>= 1, ++s;
    for(int i = 0; i < 10; i++)
        if(check(rand() % (n-1) + 1, n, r, s))
            return 0;
    return 1;
}
inline ll pollard_rho(ll n, ll c)
{
    ll k = 2, x = rand() % n, y = x, p = 1;
    for(ll i = 1; p == 1; i++)
    {
        x = (mul(x,x,n) + c) % n;
        p = y > x ? y - x : x - y;
        p = gcd(n, p);
        if(i == k) y = x, k += k;
    }
    return p;
}
void solve(ll n)
{
    if(n == 1) return;
    if(miller_rabbin(n)) {ans = max(ans, n); return;}
    ll t = n;
    while(t == n) t = pollard_rho(n, rand() % (n - 1) + 1);
    solve(t), solve(n / t);
}
int main()
{
    ll n; int T;
    scanf("%d", &T);
    while(T--)
    {
        scanf("%lld", &n);
        ans = 0, solve(n);
        if(ans == n) puts("Prime");
        else printf("%lld\n", ans);
    }
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章