1. 程式人生 > >Fermat素性測試, Miller-Rabin素性測試

Fermat素性測試, Miller-Rabin素性測試

昨天看了看spoj的第2題(坑啊~), 說的是找出 A,B之間的素數, 有T<=10組資料, A, B均小於10^9, A,B之差小於10^5。

打表是不可能了, 只能一個一個判斷。

這是我們需要一個強力的方法來判斷一個數是素數呢,還是合數。 所以可以想到這個素性測試。

Fermat素性測試

首先來說一個性質: 如果p是素數,a是小於p的正整數,那麼a^(p-1) mod p = 1。 

這個定理的證明就不多講了(鍵盤的shift鍵快要敲爛了), 請看第一句。

不過有一些合數也可以通過這樣的一個測試,當a=2時,  前10E有5597個合數。 所以我們可以試多一點數。(不過還是有反例, 請百度Carmichael數

若這個方法可以不讓合數漏網, 這個還是很容易實現的。

那麼對於一個數, 只要判斷一個素數a的p-1次方mod p的值是否為1。

對於每一個區間A,B, 先計算出a^A(a是一個數, a^A可以用快速冪),再依次判斷即可。

時間複雜度 O(T * A *(log N + L))  A為需要列舉為a的數的個數(有點繞)。

Miller-Rabin素性測試

這才是本文的重點,

再來說一個定理:如果p是素數,x是小於p的正整數,且x^2 mod p = 1,那麼要麼x=1,要麼x=p-1。

這是顯然的,因為x^2 mod p = 1相當於p能整除x^2-1,也即p能整除(x+1)(x-1)。由於p是素數,那麼只可能是x-1能被p整除(此時x=1)或x+1能被p整除(此時x=p-1)。

所以我們可以得到一個判斷n是否為素數的方法:

原來判斷素數的式子為 a^(p-1) mod p = 1, 若 p-1 % 2==0 , 那麼原式就變成了 : (a ^  x)  ^ 2 mod p = 1.  (x = p/2)

若式子不成立, 那麼它連Fermat素性測試也沒有通過,則肯定不是素數, 那麼成立以後,

若p為素數, 根據上面的式子, a^x= 1 或 a^x= p-1.

所以有兩種情況: 當x=0【即再也分解不出2因子】或 a^x= p-1 時, 我們宣佈: 這個傢伙有可能是素數。

所以實現方法如下:

不斷地提取指數n-1中的因子2,把n-1表示成d*2^r(其中d是一個奇數)。

那麼我們需要計算的東西就變成了a的d*2^r次方除以n的餘數。於是,a^(d * 2^(r-1))要麼等於1,要麼等於n-1。

如果a^(d * 2^(r-1))等於1,定理繼續適用於a^(d * 2^(r-2)),這樣不斷開方開下去,直到對於某個i滿足a^(d * 2^i) mod n = n-1或者最後指數中的2用完了得到的a^d mod n=1或n-1。

但遺憾的是,竟然還是有一些這樣的合數,能通過這樣的測試, 若a=2,通過測試的第一個合數是2407.

加上a=3, 第一個反例到了1 373 653。 這還是比較萬幸的,至少沒有能通過所有測試的合數
題目說數都是小於10E, 那麼用a=2, 3, 61是沒有問題的, 在4 759 123 141之前都是沒有問題的。

附上標程: 參見本文第一句

function pow( a, d, n:longint ):longint;
begin
   if d=0 then exit(1)
   else if d=1 then exit(a)
   else if d and 1=0 then exit( pow( a*a mod n, d div 2, n) mod n)
   else exit( (pow( a*a mod n, d div 2, n) * a) mod n);
end;

function IsPrime( a,n:longint ):boolean;
var
   d,t:longint;
begin
   if n=2 then exit(true);
   if (n=1) or (n and 1=0) then exit(false);
   d:=n-1;
   while d and 1=0 do d:=d shr 1;   // 提出因子2
   t:=pow( a, d, n );                        // 運用快速冪計算出a^d
   while ( d<>n-1 ) and ( t<>1 ) and ( t<>n-1 ) do // 即還有因子2, mod 不等於 1 或 n-1
   begin
      t:=(t * t)mod n;
      d:=d shl 1;
   end;
   exit( (t=n-1) or (d and 1=1) ); // 跳出 , mod的數等於 n-1 或 一開始就成立。
end;

所以這道題解決了。時間複雜度為O (N log N)。注意,時限為6s。