$Miller-Robbin$ 與 $Pollard Rho$ 雖然都是隨機算法,不過用起來是真的爽。html
$Miller Rabin$ 算法是一種高效的質數判斷方法。雖然是一種不肯定的質數判斷法,可是在選擇多種底數的狀況下,正確率是能夠接受的。算法
$Pollard Rho$ 是一個很是玄學的方式,用於在 $O(n^{1/4})$ 的指望時間複雜度內計算合數$n$的某個非平凡因子。優化
事實上算法導論給出的是 $O(\sqrt p)$ , $p$ 是 $n$ 的某個最小因子,知足 $p$ 與 $\frac{n}{p}$ 互質。ui
可是這些都是指望,未必符合實際。但事實上 $Pollard Rho$ 算法在實際環境中運行的至關不錯。spa
注:以上摘自洛谷。code
1. 費馬小定理htm
內容:若 \(\varphi(p)=p-1,\,p>1\),則\(a^{p}\equiv a\pmod{p}\) 或 \(a^{p-1}\equiv 1\pmod{p},\,(a<p)\)blog
證實:戳這裏遞歸
2. 二次探測定理get
內容:若是 \(\varphi(p)=p-1,\,p>1,\,p>X\) ,且\(X^2\equiv 1\pmod{p}\),那麼\(X=1\ or\ p-1\)
證實:
\(\because\) \(X^2\equiv 1\pmod{p}\)
\(\therefore\) \(p|X^2-1\)
\(\therefore\) \(p|(X+1)(X-1)\)
\(\because\) \(p\)是大於\(X\)的質數
\(\therefore\) \(p=X+1\ or\ p\equiv X-1\pmod{p}\),即\(X=1\ or\ p-1\)。
由費馬小定理,咱們能夠有一個大膽的想法:知足 \(a^{p-1}\equiv 1\pmod{p}\) 的數字 \(p\) 是一個質數。
惋惜,這樣的猜測是錯誤的,能夠舉出大量反例,如:\(2^{340}\equiv 1\pmod{341}\),然鵝 \(341=31*11\) 。
因此,咱們能夠取不一樣的 \(a\) 多驗證幾回,不過,\(\forall a<561,\,a^{560}\equiv 1\pmod{561}\),然鵝 \(561=51*11\) 。
這時,二次探測就有很大的用途了。結合費馬小定理,正確率就至關高了。
這裏推薦幾個 \(a_i\) 的值: \(2,3,5,7,11,61,13,17\)。用了這幾個 \(a_i\),就連那個被稱爲強僞素數的 \(46856248255981\) 都能被除去。
.將 \(p-1\) 提取出全部 \(2\) 的因數,假設有\(s\) 個。設剩下的部分爲 \(d\)(這裏提取全部\(2\)的因數,是爲了下面應用二次探測定理) 。
枚舉一個底數 \(a_i\) 並計算 \(x=a_i^{d}\pmod p\)。
令 \(y=x^{2}\pmod p\),若是沒有出現過 \(p-1\),那麼 \(p\) 未經過二次探測,不是質數。
不然,若底數已經足夠,則跳出;不然回到第二步。
#define ll long long ll p,a[]={2,3,5,7,11,61,13,17}; inline ll mul(ll a,ll b,ll mod) { ll ans=0; for(ll i=b;i;i>>=1) { if(i&1) ans=(ans+a)%p; a=(a<<1)%p; } return ans%p; } inline ll Pow(ll a,ll b,ll p) { ll ans=1; for(ll i=b;i;i>>=1) { if(i&1) ans=mul(ans,a,p); a=mul(a,a,p); } return ans%p; } bool Miller_Robbin(ll p) { if(p==2) return true; if(p==1 || !(p%2)) return false; ll d=p-1;int s=0; while(!(d&1)) d>>=1,++s; for(int i=0;i<=8 && a[i]<p;++i) { ll x=Pow(a[i],d,p),y=0; for(int j=0;j<s;++j) { y=mul(x,x,p); if(y==1 && x!=1 && x!=(p-1)) return false; x=y; } if(y!=1) return false; } return true; }
先判斷當前數是不是素數(這裏就可應用 \(Miller-Robbin\) ),若是是則直接返回
若是不是素數的話,試圖找到當前數的一個因子(能夠不是質因子)
遞歸對該因子和約去這個因子的另外一個因子進行分解
一個一個試確定是不行的。而這個算法的發明者採起了一種清奇的思路。(即採起隨機化算法)
咱們假設要找的因子爲p
隨機取一個 \(x、y\),不斷調整 \(x\) ,具體的辦法一般是 \(x=x*x+c\)(c是隨機的,也能夠本身定)。
取 \(p=gcd(y-x,n)\) ,若\(p \in \left(1,n\right)\) ,則找到了一個因子,就返回。
若是出現 \(x=y\) 的循環,就說明出現了循環,並不斷在這個環上生成之前生成過一次的數,因此咱們必須寫點東西來判環:咱們能夠用倍增的思想,讓\(y\)記住\(x\)的位置,而後\(x\)再跑當前跑過次數的一倍的次數。這樣不斷讓\(y\)記住\(x\)的位置,x再往下跑,由於倍增因此當\(x\)跑到\(y\)時,已經跑完一個圈。
同時最開始設定兩個執行次數\(i=一、k=2\)(即倍增的時候用) ,每次取 \(gcd\) 時 \(++i\) ;若是 \(i==k\) ,則令 \(y=x\) ,並將 \(k\) 翻倍。
#include<cstdio> #include<ctime> #include<map> #include<algorithm> using namespace std; #define rg register int #define V inline void #define I inline int #define db double #define B inline bool #define ll long long #define F1(i,a,b) for(rg i=a;i<=b;++i) #define F3(i,a,b,c) for(rg i=a;i<=b;i+=c) #define ed putchar('\n') #define bl putchar(' ') template<typename TP>V read(TP &x) { TP f=1;x=0;register char c=getchar(); for(;c>'9'||c<'0';c=getchar()) if(c=='-') f=-1; for(;c>='0'&&c<='9';c=getchar()) x=(x<<3)+(x<<1)+(c^48); x*=f; } template<typename TP>V print(TP x) { if(x<0) x=-x,putchar('-'); if(x>9) print(x/10); putchar(x%10+'0'); } int T; ll n,ans; ll a[]={2,3,5,7,11,61,13,17,24251}; template<typename TP>inline ll Gcd(TP a,TP b) {return !b?a:Gcd(b,a%b);} template<typename TP>inline ll mul(TP a,TP b,TP p) { ll ans=0; for(TP i=b;i;i>>=1) { if(i&1) ans=(ans+a)%p; a=(a<<1)%p; } return ans%p; } template<typename TP>inline ll Pow(TP a,TP b,TP p) { ll ans=1; for(TP i=b;i;i>>=1) { if(i&1) ans=mul(ans,a,p); a=mul(a,a,p); } return ans%p; } B Miller_Robbin(ll n) { if(n<2) return false; if(n==2) return true; if(n%2==0) return false; ll d=n-1;int s=0; while(!(d&1)) d>>=1,++s; for(rg i=0;i<=8 && a[i]<n;++i) { ll x=Pow(a[i],d,n),y=0; F1(j,0,s-1) { y=mul(x,x,n); if(y==1 && x!=1 && x!=(n-1)) return false; x=y; } if(y!=1) return false; } return true; } inline ll Pollard_Rho(ll n) { ll x,y,c,i,k; while(true) { ll x=rand()%(n-2)+1; ll y=rand()%(n-2)+1; ll c=rand()%(n-2)+1; i=0,k=1; while(++i) { x=(mul(x,x,n)+c)%n; if(x==y) break; ll d=Gcd(abs(y-x),n); if(d>1) return d; if(i==k) {y=x;k<<=1;} } } } V Find(ll n) { if(n==1) return; if(Miller_Robbin(n)) {ans=max(ans,n);return;} ll p=n; while(n<=p) p=Pollard_Rho(p); Find(p);Find(n/p); } int main() { read(T);srand(time(0)); while(T--) { ans=0; read(n);Find(n); if(ans==n) puts("Prime"); else print(ans),ed; } return 0; }
emmmm \(\cdots\)
這數據也太毒瘤了吧!!
看來要瘋狂卡常了
蛋定的分析一波,咱們發現除了 $Pollard-Rho$ 是 $O(n^{1/4})$ 的指望時間複雜度外, $gcd$ 和龜速乘都是 $O(\log N)$ 的。
雖然這種複雜度已經很優秀了,可對於本題的數據(\(T≤350\) 、 \(1≤n≤10^{18}\)),仍是太 \(\cdots\)
因此咱們要果斷摒棄這種很 $low$ 的龜速乘,改用一種暴力溢出的快速乘:
簡單原理: \(a \times b \ \ mod \ \ p=a \times b−\left \lfloor \frac{a \times b}{p} \right \rfloor\)
用 long double
來處理這個 \(\left \lfloor \frac{a \times b}{p} \right \rfloor\)
而後處理一下浮點偏差就能夠了。
模數較大時可能會出鍋。
不過出鍋機率很小 \(\cdots\)
以下
inline ll mul(ll a,ll b,ll mod) { a%=mod,b%=mod; ll c=(long double)a*b/mod; ll ret=a*b-c*mod; if(ret<0) ret+=mod; else if(ret>=mod) ret-=mod; return ret; }
實踐證實,戰果輝煌:$6pts -> 94pts$ !!!
雖然關於龜速乘的 $O(\log n)$ 的惡劣影響獲得了必定遏制,不過,我仍是好想 $AC$ 啊!
經過辦法1 :打表 \(\cdots\)
正確 $AC$ 姿式以下:
咱們發如今 \(Pollard-Rho\) 中若是長時間隨機化而得不到結果,\(gcd\)帶來的 \(O(\log n)\) 仍是很傷腎的!!那有沒有辦法優化呢?答案是確定的。
在生成\(x\)的操做中,龜速乘所模的數就是\(n\),而要求的就是\(n\)的某一個約數,即如今的模數並非一個質數。
根據取模的性質:若是模數和被模的數都含有一個公約數,那麼此次模運算的結果必然也會是這個公約數的倍數。因此若是咱們將若干個\((y−x)\) 相乘,由於模數是 \(n\) ,因此若是若干個\((y−x)\)中有一個與\(n\)有公約數,最後的結果定然也會含有這個公約數。
因此能夠多算幾回\((y−x)\)的乘積再來求\(gcd\) (通常連續算\(127\)次再求一次\(gcd\))。
不過\(\cdots\),若是在不斷嘗試\(x\)的值時碰上一個環,就可能會還沒算到\(127\)次就跳出這個環了,就沒法得出答案;同時,可能\(127\)次計算以後,全部\((y−x)\)的乘積都變成了\(n\)的倍數(即\(\prod_{i=1}^{127} {(y-x)} \equiv 0 \pmod{n}\) )
因此咱們能夠不只在每計算\(127\)次以後求\(gcd\)、還要在倍增時(即判環時)求\(gcd\),這樣既維護了其正確性,又判了環!!
完整\(AC\)代碼:
#include<cstdio> #include<ctime> #include<algorithm> using namespace std; #define rg register int #define V inline void #define I inline int #define db double #define B inline bool #define ll long long #define F1(i,a,b) for(rg i=a;i<=b;++i) #define F3(i,a,b,c) for(rg i=a;i<=b;i+=c) #define ed putchar('\n') #define bl putchar(' ') template<typename TP>V read(TP &x) { TP f=1;x=0;register char c=getchar(); for(;c>'9'||c<'0';c=getchar()) if(c=='-') f=-1; for(;c>='0'&&c<='9';c=getchar()) x=(x<<3)+(x<<1)+(c^48); x*=f; } template<typename TP>V print(TP x) { if(x<0) x=-x,putchar('-'); if(x>9) print(x/10); putchar(x%10+'0'); } int T; ll n,ans; ll a[]={2,3,5,7,11,61,13,17}; template<typename TP>inline ll Gcd(TP a,TP b) {return !b?a:Gcd(b,a%b);} inline ll mul(ll a,ll b,ll mod) { a%=mod,b%=mod; ll c=(long double)a*b/mod; ll ret=a*b-c*mod; if(ret<0) ret+=mod; else if(ret>=mod) ret-=mod; return ret; } template<typename TP>inline ll Pow(TP a,TP b,TP p) { ll ans=1; for(TP i=b;i;i>>=1) { if(i&1) ans=mul(ans,a,p); a=mul(a,a,p); } return ans%p; } B Miller_Robbin(ll n) { if(n<2) return false; if(n==2) return true; if(n%2==0) return false; ll d=n-1;int s=0; while(!(d&1)) d>>=1,++s; for(rg i=0;i<=8 && a[i]<n;++i) { ll x=Pow(a[i],d,n),y=0; F1(j,0,s-1) { y=mul(x,x,n); if(y==1 && x!=1 && x!=(n-1)) return false; x=y; } if(y!=1) return false; } return true; } inline ll Pollard_Rho(ll n) { while(true) { ll x=rand()%(n-2)+1; ll y=rand()%(n-2)+1; ll c=rand()%(n-2)+1; ll i=0,k=1,b=1; while(++i) { x=(mul(x,x,n)+c)%n; b=mul(b,abs(y-x),n); if(x==y || !b) break; if(!(i%127) || i==k) { ll d=Gcd(b,n); if(d>1) return d; if(i==k) y=x,k<<=1; } } } } V Find(ll n) { if(n<=ans) return; if(Miller_Robbin(n)) {ans=max(ans,n);return;} ll p=Pollard_Rho(n); while(n%p==0) n/=p; Find(p),Find(n); } int main() { read(T);srand(time(0)); while(T--) { ans=0; read(n);Find(n); if(ans==n) puts("Prime"); else print(ans),ed; } return 0; }