1. 程式人生 > 實用技巧 >Miller-Rabin學習筆記

Miller-Rabin學習筆記

\(Miller-Rabin\)

\(Miller-Rabin\) 用於判定一個大整數是不是素數,且速度非常快
應該是 \(O(klog^3n)\),其中 \(k\) 為測試的次數,\(n\) 為要判定的數
演算法本質上是一種概率演算法,存在誤判的可能性,但是出錯的概率非常小。出錯的概率到底是多少,存在嚴格的理論推導。(網上copy的)

兩個重要的東西

\(Farmet\)測試

根據費馬小定理,如果 \(p \in \mathbb P\)\(\gcd(a , p) = 1\) 那麼 \(a^{p-1} \equiv 1 \pmod p\)。它給我們提供了一種檢驗素數的思路:對於數n,它的基本思想是不斷地選取在 \([2,n-1]\)
的數讓它為 \(a\) 並檢驗是否每次都有 \(a^{n-1} \equiv 1 \pmod n\),如果沒有,那麼 \(n\) 肯定不是素數

其實有它我們就可以大概率判定素數了
為什麼是大概率呢?
誰告訴你費馬小定理的逆定理是成立的?
也就是說費馬小定理的逆定理並不成立,換言之,滿足了 \(a^{n-1} \equiv 1 \pmod n\)\(n\) 也不一定是素數。

例子:1819年有人發現了費馬小定理逆命題的第一個反例:雖然2的340次方除以341餘1,但341=11*31,後來,人們又發現了561, 645, 1105等數都表明a=2時Fermat小定理的逆命題不成立。雖然這樣的數不多,但不能忽視它們的存在。統計表明,在前10億個自然數中共有50847534個素數,而滿足 \(2^{n-1} ≡ 1 \pmod n\)

的合數n有5597個
如果用費馬小定理的逆命題來判斷一個正整數n是不是素數,在前10億個自然數中出錯的可能性為 0.011%
這個出錯的可能性還是很高的,但是仍然可以用這個技巧來排除大量的合數,這種方法就是費馬檢測

上面的做法中隨機地選擇 \(a\) ,很大程度地降低了犯錯的概率。但是仍有一類數,上面的做法並不能準確地判斷。
對於合數 \(n\) ,如果對於所有正整數 \(a\)\(a\)\(n\) 互素,都有同餘式 \(a^{n-1} ≡ 1 \pmod n\) 成立,則合數 \(n\) 為卡邁克爾數(\(\texttt{Carmichael Number}\)),又稱為費馬偽素數。
比如, \(561 = 3 \times 11 \times 17\)

就是一個卡邁克爾數。
而且我們知道,若 \(n\) 為卡邁克爾數,則 \(m = 2^n-1\) 也是一個卡邁克爾數,從而卡邁克爾數的個數是無窮的。

於是 \(Farmet\) 測試變得很玄學

於是我們可以愉快清楚第二位 \(boss\)

https://www.cnblogs.com/fzl194/p/9046117.html
http://oi-wiki.com/math/prime/

二次探測定理

Code

#include<cstdio>
#include<ctime>
#include<algorithm>
using namespace std;
typedef long long LL;

inline LL fmul(LL x , LL y , LL p)
{
	LL res = 0;
	while (y)
	{
		if (y & 1) res = (res + x) % p;
		y >>= 1 , x = (x + x) % p;
	}
	return res;
}

inline LL fpow(LL x , LL y , LL p)
{
	LL res = 1;
	while (y)
	{
		if (y & 1) res = fmul(res , x , p);
		y >>= 1 , x = fmul(x , x , p);
	}
	return res;
}


inline int Miller_Rabin(LL m , int test_time)
{
	if (m < 3) return (m == 2);
	if (!(m & 1)) return 0;
	
	LL d = m - 1;
	int b = 0;
	while (!(d & 1)) d = d >> 1 , b++;
	
	srand((unsigned)time(NULL));
	for(register int i = 0; i < test_time; i++)
	{
		LL a = rand() % (m - 3) + 2 , v = fpow(a , d , m) , u = 0;
		for(register int j = 0; j < b; j++)
		{
			u = fmul(v , v , m);
			if (u == 1 && v != 1 && v != (m - 1)) return 0;
			v = u;
		}
		if (v != 1) return false;
	}
	return 1;
}

int main()
{
	int n;
	scanf("%d" , &n);
	LL m;
	while(n--)
	{
		scanf("%lld" , &m);
		if (Miller_Rabin(m , 6)) printf("Prime\n");
		else printf("Not prime\n");
	}
}