各种友(e)善(xin)数论总集,从入门到绝望2
目錄
- 前置芝士
- 二進制的GCD
- 快速乘
- O(1)
- O(log)
- floyd提出的判環法
- 判環
- 找環
- 生日悖論
- Miller_rabin
- 前言
- 二次探測
- 代碼
- Pollard-Rho
- 前言
- 例題
- 構建隨機數列
- 優(ka)化(chang)
- 注意事項
- 時間復雜度證明
- 代碼
因為原來的那篇已經很多了,所以在此寫上第二篇。
這一章可以說是緊緊圍繞的素數的主旨展開的。
@
前置芝士
二進制的GCD
二進制的GCD用的是更相減損法。
首先,我們有兩個數字\(x,y\)。
常數好像比GCD更小,復雜度都是\(log\),但是貌似跑起來差不多,寫寫吧,反正都差不多了,還更穩一點,你說是吧。
快速乘
博主博主,平常\(O(1)\)都已經如此之快,難道可以\(O(0)\)?
不不不,都一樣,只不過算的是\(x*y\%z\),因為有時候\(x*y\)溢出了long long,但是結果并沒有,所以發明了快速乘。
O(1)
\(O(1)\)版的非常簡單。\(x*y\%z=x*y-(x*y/z)*z\)(在C++環境下),但是這個有什么特殊的嗎?
就是用溢出對待溢出,我們先用long double(16位)得出(x*y/z)(你在比賽的時候,可以先用\(sizeof(long\) \(double)\)得出你電腦的long double是幾位的,如果不是\(16\)位的話,那評測機應該也是,為了穩妥還是用\(log\)的吧)。
然后我們乘一下,兩邊可能會溢出,但是我們還是能減出正確的結果。
另外還有一坨精度問題,模數大的話還是少用吧。
inline LL ksc(LL x,LL y,LL z) {LL c=(LD)x*y/z+0.5;LL ans=x*y-c*z;return ans<0?ans+z:ans; }O(log)
有沒有什么穩得一批又好用的快速乘?
當然后,假設又有個\(x,y,z\)。
我們把\(x\)拆成二進制:\(c_{n}*2^{n}+c_{n-1}*2^{n-1}+....\),而\(c\)的取值只能為\(0,1\)
然后\(x*y\)在乘法分配率一下:\(y*c_{0}*2^{0}+y*c_{1}*2^{1}+...\),那么我們只要邊乘邊模不就好起來了嗎。
floyd提出的判環法
判環
你以為是floyd,不是,是這樣的,假設一個鏈表有環,怎么判環,我們這樣想:\(y\)以\(x\)兩倍的速度奔跑,那么當\(x,y\)相遇時,\(y\)剛好跑完幾圈了,就退出。
找環
其實還有個擴展,如何找到環的起始位置。
我們設鏈表頭走到環開始的地方步數為\(m\),從鏈表頭走到相遇地點的步數是\(m+k\),然后環的長度為\(n\),\(x,y\)分別走了\(X,Y\)圈。
那么\(S_{x}=m+k+Xn,S_{y}=m+k+Yn\),然后又因為\(S_{y}=2S_{x}\),所以\(S_{x}=(Y-X)n\)。
也就是說兩人走的距離肯定是\(n\)的倍數。
然后我們再把\(x\)提到了鏈表開始的地方,讓兩個人繼續開始走,當走了\(m\)步(\(x\)在環開始的地方),那么因為說了\(S_{y}=2S_{x}=2(Y-X)n\),也就是說\(y\)走的步數應該是\(n\)的倍數,也就是說當第一次相遇的時候,他應該離走完這個環到環開始的地方為\(m\),所以\(y\)也到了環開始的地方,所以\(x,y\)將會在環開始的地方相遇。
可惜這里不用。
生日悖論
首先我們來看看,現在我們來選數字,在1-100之間,如果我們選一個數字,那么是1的概率則是\(\frac{1}{100}\),但是如果我們選兩個數字,然后取差的絕對值,會怎樣?我們選一個數字,然后選到他周圍的兩個數字的概率就變成了\(\frac{1}{50}\)了!(忽略第一個數字\(1\)和\(100\)的情況,那還是\(\frac{1}{100}\)),難道多元能增加概率!
沒錯,這就是生日悖論的內容。
生日悖論的重要思想是什么,\(1-x\)的范圍,如果有\(\sqrt{x}\)個數字的話,重復的概率就會高達\(50\%\),恐不恐怖。
至于證明,在這里貼上大佬的證明。
Miller_rabin
相信在第一章里面,你們已經學會了費馬小定律了,那就不講了QMQ。
前言
我們都知道判斷一個數字是不是素數,有一種方法就是試除法,直接從\(2\)枚舉到\(\sqrt{p}\),但是有沒有一種方法,能比\(O(\sqrt{p})\)還快有準確無誤呢?
答案是并沒有,但是如果你要求的是很大概率的話,打我可以告訴你的是,Miller_rabin就是這么一種算法,基本上準確無誤,就連強偽素數都能跑過去,是什么呢?
二次探測
我們都知道,選取一個\(p\)數字,然后用費馬小定理判斷一下,如果不是\(1\),那就不是素數,但是存在這么一種數字,能滿足費馬小定理但是不是素數的一類數字,我們又要怎么判斷呢?
這里就要引入一個定理了,這個定理可以很大概率的判斷是不是素數,加上費馬小定理。
如果\(p\)是質數且\(a^2≡1(\mod p)(a<p)\),那么\(a=1,p-1\)。
我們可以來證明一下:
\[a^2≡1(\mod p)\]
\[a^2-1≡(\mod p)\]
\[(a+1)(a-1)≡0(\mod p)\]
那么,因為\(a<p\)且\(p\)是質數,所以\(a=1,p-1\)。
那么我們就可以把\(p-1\)分成\(2^{t}*k\),然后隨機選取一個值\(x\),然后計算\(x^{k}\),然后繼續不斷取平方:\(x^{2^{i}*k}\),然后不斷的用二次探測來檢測,更重要的是我們最后還可以用用費馬小定理,當然,\(x\)我們可以手動取幾個素數來多判幾次,不知道為什么,素數成功概率大一點QMQ。
很明顯是\(log\)的。
代碼
inline LL ksc(LL x,LL y,LL z) {LL c=(LD)x*y/z+0.5;LL ans=x*y-c*z;return ans<0?ans+z:ans; } inline LL ksm(LL x,LL m,LL mod) {if(m==0)return 1%mod;LL ans=1;while(m>1){m&1?ans=ksc(ans,x,mod):0;x=ksc(x,x,mod);m>>=1;}return ksc(ans,x,mod); } inline int log2(LL &x) {int ans=0;while(x%2==0)ans++,x>>=1;return ans; } int su[]={2,3,5,7,11,23,29,61}; inline bool pd(LL x)//判斷一個素數 {for(int i=0;i<=7;i++){if(x<=su[i])return 1;LL y=x-1;int tt=log2(y);y=ksm(su[i],y,x);while(tt--){LL z=ksc(y,y,x);if(z==1 && y!=1 && y!=x-1)return 0;y=z;}if(y!=1)return 0;}return 1; }Pollard-Rho
前言
你是否想快速的分解一個素數?
想嗎?少年。
---來自SaDiao博主的一席話。
例題
都看到了,就是想咯QMQ。
例題
假設我們要分的數字是\(p\)
構建隨機數列
我們構建一個隨機函數\(x_{i}=x_{i-1}*x_{i-1}+C(\mod p)\)(\(C\)為我們自己定的常數),這么強?
同時\(x_{1}=2\),我們發現這個序列每個都跟前面的數字有關,那么不就是類似鏈表了嗎?而且因為是模了后序列,所以會有環(根據生日悖論,出現相同的數字概率是\(O(\sqrt{p})\)),就可以用\(floyd\)判環法了。
我們再設一個函數:\(y_{i}=x_{i-1}*x_{i-1}+C(\mod q)\)(\(q\)為\(p\)的一個質因數),\(y_{1}=2\),那么這個序列也會出現循環節的,我們再在兩個循環節上找到兩個位置\(i,j\),使得\(i<j,l(i)=l(j)\),然后我們就會發現\(|x_{i}-x_{j}|\)含有\(q\),也就是\(gcd(|x_{i}-x_{j}|,p)≠1\),那么我們就可以找到一個素數了。
但是我們并不知道\(q\),我們又怎么找到\(i,j\)呢,我們會發現其實\(i,j\)就是\(y\)數列循環節上的對應位置,而上面也只是提供了一種可行性,也就是說我們可以用\(floyd\)找環法來在\(x\)數列中找,如果\(gcd\)為\(1\),那么繼續找環,如果\(gcd\)為\(p\)(差為0就會這樣),說明我們找到了模數為\(q\)的環,可惜也是模數為\(p\)的環,那么我們就退出,然后\(C++\),如果兩個都不是,那么我們不就找到了一個質因數了嗎?
再看看概率有多大,原本我們找兩個數字的差找到質因數的概率應該比較小,但是如果我們是\(gcd≠1\)的話,那概率不就大了嗎?而且期望的循環節大小為\(\sqrt{p}\),不就好起來了嗎?
而且判斷一個數字是不是素數就靠Miller_rabin了。
優(ka)化(chang)
我們要發現一個事情:\(x\%z\),可以等同于\(z*((x/gcd(x,z))\%(z/gcd(x,z)))\)。
那么這有什么用呢?我們可以把幾個差乘起來,然后模一下(如果出現了質因子的話不會因為模了而消失掉,上面寫了),至于乘幾次,我選擇的是\(127\),當然,\(pow(p,0.1)\)也有人用,不要太大就可以了,我們后面則叫乘了\(step\)次。
那么我們也是乘完后GCD。
然后自我感覺良好,優化了不少的常數。
注意事項
我們會發現,打完代碼有事后還是會卡住的。
為什么,因為\(4\)是個神奇的數字,我們可以把\(C\)從\(1\)到\(4\)枚舉一遍,會發現差統統不會涉及到\(2\),而其他\(2\)的次方,比如\(2^{i}\),在\(C=2^{i}-4\)的時候,肯定跳一次就能得到結果,\(x=0,y=C\),然后就可以把\(2\)篩出來,為什么\(4\)不行,因為\(C=0\)了,所以我們就只會一遍遍的得到\(4\)然后重來。
那我們特判\(4\)不就行了?不不不,特判要講究藝術。
你想想,我們要是特判\(\%2==0\)不是一樣的嗎,而且還造福了其他的數字,尤其是\(2\)的次方,不加這個很有可能就老是到\(2^{i}-4\)才跳出來。
時間復雜度證明
一次的Pollard-Rho的復雜度是多少?(這道題目得用幾次Pollard-Rho)
設\(N=A*B(A<=B)\),那么\(A<=\sqrt(N)\),我們是在\(x\)找\(A\)的循環節,期望復雜度為\(O(\sqrt{A})\),那么不就是\(O(N^{\frac{1}{4}})\)嗎?
但是其實不然,因為加上GCD什么亂起八糟的,正宗的應該是:
\(O(\frac{N^{\frac{1}{4}}logN}{step}+logN)\),但原本就是玄學算法你加這么多干嘛,而且在long long范圍內我們的\(step\)肯定大于\(logN\),畢竟我們的\(step\)原本就是\(127\)嗎。
所以我們還是寫\(O(N^{\frac{1}{4}})\),好看又好寫。
代碼
開了O2在luogu跑了600+ms,快的飛起,不開也有1.44s了,這不就快的飛起了嗎。
可以試試這個跑不跑得出結果46856248255981。
強偽素數呀,都跑過去了。
#include<cstdio> #include<cstring> #include<cmath> using namespace std; typedef long double LD; typedef long long LL; inline LL zabs(LL x){return x<0?-x:x;} inline LL mymin(LL x,LL y){return x<y?x:y;} inline LL mymax(LL x,LL y){return x>y?x:y;} inline LL ksc(LL x,LL y,LL z) {LL c=(LD)x*y/z+0.5;LL ans=x*y-c*z;return ans<0?ans+z:ans; } inline LL ksm(LL x,LL m,LL mod) {if(m==0)return 1%mod;LL ans=1;while(m>1){m&1?ans=ksc(ans,x,mod):0;x=ksc(x,x,mod);m>>=1;}return ksc(ans,x,mod); } inline int log2(LL &x) {int ans=0;while(x%2==0)ans++,x>>=1;return ans; } int su[]={2,3,5,7,11,23,29,61}; inline bool pd(LL x)//判斷一個素數 {for(int i=0;i<=7;i++){if(x<=su[i])return 1;LL y=x-1;int tt=log2(y);y=ksm(su[i],y,x);while(tt--){LL z=ksc(y,y,x);if(z==1 && y!=1 && y!=x-1)return 0;y=z;}if(y!=1)return 0;}return 1; } inline LL gcd(LL x,LL y)//實測二進制版GCD只比原來的快了20+ms,估計是因為優化減少了GCD的調用次數,凸顯不出優勢。 {int ans=0;while(x && y){if(x&1 && y&1){y>x?x^=y^=x^=y:0;x=(x-y)>>1;}else if(x&1)y>>=1;else if(y&1)x>>=1;else x>>=1,y>>=1,ans++;}return (x+y)<<ans; } inline LL Pol(LL now,LL step,LL add) {if(now%2==0)return 2;//防止毒瘤的4的情況 LL x=2,y=2,d=1;while(1){LL tx=x,ty=y;for(int i=1;i<=step;i++){x=ksc(x,x,now)+add;x>=now?x-=now:0;y=ksc(y,y,now)+add;y>=now?y-=now:0;y=ksc(y,y,now)+add;y>=now?y-=now:0;d=ksc(d,zabs(x-y),now);}d=gcd(d,now);if(d==1)continue;else if(d!=now)return d;x=tx;y=ty;for(int i=1;i<=step;i++){x=ksc(x,x,now)+add;x>=now?x-=now:0;y=ksc(y,y,now)+add;y>=now?y-=now:0;y=ksc(y,y,now)+add;y>=now?y-=now:0;d=gcd(zabs(x-y),now);if(d!=1)return d%now;}} } inline LL work(LL n) {if(pd(n) || n==1)return n;LL tmp=0,step=127/*玄學步數*/,add=1;while(!tmp)tmp=Pol(n,step,add++);//if(n/tmp<tmp)tmp=n/tmp;//使得n/tmp>=tmpLL ans=work(n/tmp);if(ans>=tmp)return ans;return mymax(ans,work(tmp));//實現上的一個優化,優化空間小,但是能優化,而且不會耗多少空間,基本正優化 } LL n; int main() {int T;scanf("%d",&T);while(T--){scanf("%lld",&n);LL ans=work(n);if(ans==n)printf("Prime\n");else printf("%lld\n",ans);} return 0; }轉載于:https://www.cnblogs.com/zhangjianjunab/p/11365009.html
總結
以上是生活随笔為你收集整理的各种友(e)善(xin)数论总集,从入门到绝望2的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 自己挖的坑自己填--docker创建实例
- 下一篇: Java 调用http接口(基于OkHt