【BZOJ 3667】
第一行:CAS,代表數據組數(不大於350),以下CAS行,每行一個數字,保證在64位長整形範圍內,並且沒有負數。你需要對於
每個數字:第一,檢驗是否是質數,是質數就輸出Prime
第二,如果不是質數,輸出它最大的質因子是哪個。
檢驗一個數是否是質數的樸素方法是的,並不能滿足這道題的需求。
其實直到現在,人們對一個數的素性判定依舊沒有正確率爲100%的方法。但素數簡直是太重要了,我們生活中方方面面都要用到它(如著名的RSA加密算法)。所以,爲了應付日常的生產需求,人們發明了一個能較快判斷素數的算法。雖然正確率達不到100%,但如果我們的參數選取適當,就可以做到十分的準確。這就是今天要介紹的Miller-Rabin素性測試。
這個算法的準確性保證來自於這兩個定理:
第二個定理的證明比較簡單:
終於可以開始正題了。
但是在開始前,還是得先講一個東西。
·Miller-Rabin素性測試的前身-費馬測試。
還記得費馬小定理嗎?
雖然滿足上式的數並不一定是一個素數,但在很多情況下是成立的。所以,我們可以隨便選一個,然後代入上式驗證即可。
但可惜的是,上面的方法的容錯率還達不到我們的需求。怎麼辦呢?,接下來,該快用你無敵的白金之星想想辦法啊正式介紹Miller-Rabin素性測試了。
首先我們對偶數特判(反正它絕對不是質數)。對於待測數,我們隨機選擇一個底,再嘗試把寫成形如下形式
寫成這樣有什麼用呢?我們先算,然後嘗試用費馬小定理。如果此時它通過了本次測試,根據二次探測定理,此時一定爲1或。如果不是,則一定不是質數。否則,我們將這個東西再平方,重複上述過程,直到這s次平方全部用完,最後再判斷是否符合費馬小定理即可。
實踐中,選擇十個隨機數進行判定,其準確度已經相當高了。如果還不滿意,則可以嘗試使用隨機的素數。
這個算法的時間複雜度僅爲,已經可以應付大部分情況了。
現將代碼貼在下面:
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。每次隨機生成一個數,嘗試去構造另一個數,使,然後將作爲的一個因子,遞歸地處理這個問題。
那麼這個到底是怎麼生成的呢?這個算法的發明人提出按照下列公式生成這個數列,直到出現重複元素爲止。
接下來遇到了一個問題:如果找遍這個數列,都不滿足怎麼辦?
換一個c和再試一下即可。
……
我這麼非,一直都找不出來那我不涼了?
Pollard-Rho的效率保證:生日悖論
問一個問題:在一個班中,隨機選k()個人,他們的生日相同的概率。
不難列出式子:
經計算髮現,當時,超過了50%;當k爲59時,ans接近100%。然而n多年的讀書經歷告訴我們並非如此。
這個東西告訴我們:如果我們採取某種組合方式,從一個樣本空間中隨機取樣,抽中答案的概率比單次抽取的概率高。
再看回上面的算法流程。我們從“尋找使”改爲“尋找使”。根據上面的結論,這種方式將降低Pollard-Rho算法的重選次數,從而將時間開銷控制在可以接受的範圍內。
(網上的很多博客中寫的複雜度是。)
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);
}
}