摘自:http://blog.csdn.net/pi9nc/article/details/27209455
看了好久沒看懂,最后在這篇博客中看明白了。
費馬定理的應用,加上二次探測定理。
Fermat素數測試
1819年有人發現了Fermat小定理逆命題的第一個反例:雖然2的340次方除以341余1,但341=11*31。后來,人們又發現了561, 645, 1105等數都表明a=2時Fermat小定理的逆命題不成立。人們把所有能整除2^(n-1)-1的合數n叫做偽素數(pseudoprime)。
不滿足
的n一定不是素數;如果滿足的話則多半是素數。這樣,一個比試除法效率更高的素性判斷方法出現了:制作一張偽素數表,記錄某個范圍內的所有偽素數,那么所有滿足
且不在偽素數表中的n就是素數。之所以這種方法更快,是因為我們可以使用二分法快速計算
的值(快速冪)。
然而不借助偽素數表的時候,算法出錯的概率太高,需要改進.
我們剛才只考慮了a=2的情況。一個合數可能在a=2時通過了測試,但a=3時的計算結果卻排除了素數的可能。於是,人們擴展了偽素數的定義,稱滿足a^(n-1) mod n = 1的合數n叫做以a為底的偽素數(pseudoprime to base a)。
隨機選擇若干個小於待測數的正整數作為底數a進行若干次測試,只要有一次沒有通過測試就立即把這個數扔回合數的世界。這就是Fermat素性測試。
費馬小定理畢竟只是素數判定的一個必要條件.滿足費馬小定理條件的整數n未必全是素數.有些合數也滿足費馬小定理的條件***.這些合數被稱作Carmichael數,前3個Carmichael數是561,1105,1729. Carmichael數是非常少的.在1~100000000范圍內的整數中,只有255個Carmichael數.
***費馬小定理的前提是a和n互質。當n本身就是素數的時候如果a<n那么a和n始終互素;但n不是素數時a和n不互素的話不能用費馬小定理。也就是說,Carmichael數需要排除a和n不互素的情況.
利用下面的二次探測定理可以對上面的素數判定算法作進一步改進,以避免將Carmichael數當作素數.
Miller_Rabin素數測試算法
二次探測定理優化
Miller和Rabin兩個人的工作讓Fermat素性測試邁出了革命性的一步,建立了Miller-Rabin素性測試算法。新的測試基於下面的定理:
如果p是素數,x是小於p的正整數,且
,那么要么x=1,要么x=p-1。
這是顯然的,因為
相當於p能整除
,也即p能整除(x+1)(x-1)。
由於p是素數,那么只可能是x-1能被p整除(此時x=1) 或 x+1能被p整除(此時x=p-1)。
我們下面來演示一下上面的定理如何應用在Fermat素性測試上。前面說過341可以通過以2為底的Fermat測試,因為2^340 mod 341=1。如果341真是素數的話,那么2^170mod 341只可能是1或340;當算得2^170 mod 341確實等於1時,我們可以繼續查看2^85除以341的結果。我們發現,2^85 mod 341=32,這一結果摘掉了341頭上的素數皇冠
這就是Miller-Rabin素性測試的方法。不斷地提取指數n-1中的因子2,把n-1表示成
(其中d是一個奇數)。那么我們需要計算的東西就變成了
除以n的余數。於是,
要么等於1,要么等於n-1。如果
等於1,定理繼續適用於
,這樣不斷開方開下去,直到對於某個i滿足
或者最后指數中的2用完了得到的
。這樣,Fermat小定理加強為如下形式:
盡可能提取因子2,把n-1表示成
,如果n是一個素數,那么或者
,或者存在某個i使得
( 0<=i<r ) (注意i可以等於0,這就把
的情況統一到后面去了)
Miller-Rabin素性測試同樣是不確定算法,我們把可以通過以a為底的Miller-Rabin測試的合數稱作以a為底的強偽素數(strong pseudoprime)。第一個以2為底的強偽素數為2047。第一個以2和3為底的強偽素數則大到1 373 653。
Miller-Rabin算法的代碼也非常簡單:計算d和r的值(可以用位運算加速,即快速積,快速冪),然后二分計算
的值,最后把它平方r次。
/*對應hoj 1356 Prime Judge*/ #include <cstdio> #define MT 5 using namespace std; typedef long long ll; int prime[] = {2, 3, 7, 61, 24251}; inline ll mulmod(ll a, ll b, ll k) {//*標出了核心語句 // a %= k; // b %= k; if (b < 0) {///將b變為正的 a = -a; b = -b; } ll re = 0, temp = a; ///re為最終結果,temp保持循環.re需要temp的時候,就加一下,否則temp繼續累乘 while (b) { if (b & 1) re += temp;///二進制思想,需要即加* // re %= k; b >>= 1;///下一位等待檢測** temp <<= 1;///temp一直累乘*** // temp %= k; } return re%k;*/ /*實際上呢,用以上的函數在hoj 1356是會TLE的(mod太多次了...)~應該用下面的方法...*/ return (a*b)%k;//-_-b } //此時不需要再模,於是只剩下核心語句~快速冪和快速積都是二進制思想,核心是一樣的 inline ll powermod(ll a, ll b, ll k) { ll re = 1, temp = a; while (b) { if (b & 1) re = mulmod(re, temp, k);//只是把"加"入答案變為"乘"入答案 temp = mulmod(temp, temp, k); b >>= 1; } return re; } int TwiceDetect(ll a, ll b, ll k) { int t = 0; ll x, y; while ((b & 1) == 0) { b >>= 1; t++; } /// b = d * 2^t; b = d; y = x = powermod(a, b, k);/// x = y = a^d % n ///二次探測定理是反向遞歸的,當然也可以用如下的正向迭代法去測試 while (t--) { y = mulmod(x, x, k); if (y == 1 && x != 1 && x != k - 1)///注意y!=1的時候是不做判斷的,即對應 return 0;///遞歸時在某一環節x==p-1的情況,對此x開方則無意義,但是迭代的話不能break,只能ignore並繼續. x = y;///繼續向高次迭代,那么至少最后一次應該是等於1的(Fermat小定理) } return y; } bool Miller_Rabin(ll n) { int i; ll tmp; for (i = 0; i < MT; i++) { tmp = prime[i]; if (n == prime[i]) return true; if (TwiceDetect(tmp, n - 1, n) != 1) break; } return (i == MT); } int main() { ll n; while (scanf("%lld", &n) == 1) { if ((n > 1) && Miller_Rabin(n)) { printf("YES\n"); } else { printf("NO\n"); } } return 0; }
對於大數的素性判斷,目前Miller-Rabin算法應用最廣泛。一般底數仍然是隨機選取,但當待測數不太大時,選擇測試底數就有一些技巧了。比如,如果被測數小於4 759 123 141,那么只需要測試三個底數2, 7和61就足夠了。當然,你測試的越多,正確的范圍肯定也越大。如果你每次都用前7個素數(2, 3, 5, 7, 11, 13和17)進行測試,所有不超過341 550 071 728 320的數都是正確的。如果選用2, 3, 7, 61和24251作為底數,那么10^16內唯一的強偽素數為46 856 248 255 981。這樣的一些結論使得Miller-Rabin算法在OI中非常實用。通常認為,Miller-Rabin素性測試的正確率可以令人接受,隨機選取k個底數進行測試算法的失誤率大概為4^(-k)。
