1. 程式人生 > 實用技巧 >Pollard-Rho演算法 學習筆記

Pollard-Rho演算法 學習筆記

Miller Rabin 素性測試

假設我們要判定一個數 \(m\) 是否是素數。

Fermat素性測試

可以隨便找到一個數 \(a\),然後判定 \(a^{m - 1} \bmod m\) 是否為 \(1\)

如果我們找到 \(k\)\(a\) 都滿足 \(a^{m - 1} \equiv 1\),那麼我們就把 \(m\) 判定為素數。

這樣子的判定事實上是不對的,因為為有很多 (無限個) 合數滿足對於任意 \(a\)\(a^{m - 1} \equiv 1 \pmod m\)

二次探測定理

對於任意奇素數 \(p\)\(x^2 \equiv 1 \pmod p\) 的解只有 \(1\)

\(p - 1\)

證明:\(x^2 - 1 \equiv 0 \pmod p\)\((x + 1) (x - 1) \pmod p\),因此要麼 \(p | x + 1\),要麼 \(p | x - 1\),因此 \(x\)\(1\)\(p - 1\)


我們把二次探測定理和費馬小定理配合起來使用。

\(n - 1\) 分解成 \(t \times 2^k\)。我們判一下 \(k = a^t \pmod m\)。如果不是 \(1\) 且不是 \(m - 1\),那麼繼續判斷。

如果 \(m\) 是素數,那麼平方之後為 \(1\) 且原先不是 \(1\) ,那麼一定是由 \(m - 1\)

平方而來。所以我們只要平方,然後如果沒有遇到 \(m - 1\) 就認為他不是素數。

一次成功判定的概率是 \(\frac{3}{4}\)

(程式碼見下面 Pollard-Rho 演算法 的)

Pollard-Rho 演算法

生日悖論

隨機找 \(23\) 個人,他們中有人在同一天生日的概率是 \(50 \%\)

推廣:隨機寫在 \([1, n]\) 中的數,它們中有兩個數相同的期望次數是 \(\sqrt n\)

演算法

(假設我們要找 \(n\) 的因子)

我們生成一個序列 \(F\)\(f_i = F(f_{i - 1})\)\(F(x)\) 是一個函式,這裡 \(F(x) = ( x^2 + seed ) \bmod n\)

\(seed\) 是隨機選擇的一個數。

\(F(x)\) 是幾乎隨機的,\(F(x) \bmod q\)\(F(x) \bmod n\) 也幾乎隨機。

假設 \(n\) 有一個因數 \(q\) ,那麼 \(F(x) \bmod q\) 必然會重複,因此有迴圈節,且迴圈節長度期望為 \(\sqrt q\)

如果找到迴圈節,那麼用迴圈節中重複的兩個數 \(val1, val2\)\(val1 \equiv val2 \pmod q\),我們可以用 \(\gcd(n, |val1 - val2|)\) 來找到 \(q\)

如何找迴圈節?

可以設兩個變數 \(a\)\(b\) (初值相等),每次讓 \(a = F(a), b = F(F(b))\)。最後他們會相遇,這樣子我們就找到了環。

找環的期望步數是 \(\sqrt q\) 的。\(q\) 可以取到 \(n\) 的最小因數,所以步數是期望 \(n^{\frac{1}{4}}\)

但是我們找環的時候每次都要做一遍 \(\gcd\),時間複雜度 \(\Theta(n^{\frac{1}{4}} \log n)\)

但是我們可以優化:分段處理。每 \(lim\) 個數處理一次,處理出 \(mul = \prod\limits_{i = 1}^{lim} p_i \bmod n\),然後判一下 \(\gcd(mul, n)\) 是否為 \(1\)

如果 \(\gcd\) 不為 \(1\) 就判一下找到環的位置。時間複雜度變成了 \(\Theta (n^{\frac{1}{4}})\)

程式碼:

#include<bits/stdc++.h>
#define L(i, j, k) for(int i = j, i##E = k; i <= i##E; i++)
#define R(i, j, k) for(int i = j, i##E = k; i >= i##E; i--)
#define ll long long
#define ull unsigned long long
#define db double
#define pii pair<int, int>
#define mkp make_pair
using namespace std;
mt19937_64 orz(233);
const int N = 1e5 + 7;
ll Sum(ll aa, ll bb, ll mod) {
	aa += bb;
	return aa >= mod ? aa - mod : aa;
}
ll Mul(ll a, ll b, ll mod) {
	ll sav = (a * b - (ll)((long db) a * b / mod + 0.1) * mod) % mod; 
	return sav < 0 ? sav + mod : sav;
} 
ll mypow(ll x, ll y, ll mod) {
	ll res = 1;
	for(; y; x = Mul(x, x, mod), y >>= 1) if(y & 1) res = Mul(res, x, mod);
	return res;
} 
const int testp[8] = {2, 3, 5, 7, 13, 19, 61, 233};
bool Miller_Rabin(ll x) {
	if(x < 3) return x == 2;
	if(x <= testp[7]) {
		L(i, 2, sqrt(x)) if(x % i == 0) return 0;
		return 1;
	}
	int k, j; ll a;
	for(k = 0, a = x - 1; ! (a & 1); k ++) a >>= 1;
	L(i, 0, 7) {
		ll v = mypow(testp[i], a, x);
		if(v == 1 || v == x - 1) continue;
		for(j = 0; j < k; j ++) {
			v = Mul(v, v, x);
			if(v == x - 1) break;
		}
		if(j == k) return 0;
	}
	return 1;
}
int total;
ll f[N], seed;
ll F(ll x, ll mod) {
	return Sum(Mul(x, x, mod), seed, mod);
}
ll Pollard_Rho(ll x) {
	total = 0, seed = orz() % (x - 1) + 1;
	ll val1 = rand() % (x - 1) + 1, val2 = val1, mul = 1, sav;
	while(1) {
		val1 = F(val1, x), val2 = F(F(val2, x), x);
		f[++total] = abs(val1 - val2);
		mul = Mul(mul, f[total], x);
		if(total == 127) {
			if(__gcd(mul, x) > 1) {
				L(i, 1, total) if((sav = __gcd(f[i], x)) > 1) return sav;
				return x; 
			}
			total = 0;
		}
	}
}
int tot;
ll ans[N];
void edsolve(ll x) {
	if(x < 2) return;
	if(Miller_Rabin(x)) return ans[++tot] = x, void();
	ll sav = Pollard_Rho(x);
	while(sav == x) sav = Pollard_Rho(x);
	edsolve(sav), edsolve(x / sav);
}
void solve(ll x) {
	tot = 0;
	L(i, 2, testp[7]) while(x % i == 0) ans[++tot] = i, x /= i;
	edsolve(x), sort(ans + 1, ans + tot + 1);
}
ll n;
void Main() {
	scanf("%lld", &n), tot = 0;
	if(Miller_Rabin(n)) puts("Prime");
	else solve(n), printf("%lld\n", ans[tot]);
}
int main() {
	int T; scanf("%d", &T);
	while(T--) Main();
	return 0;
}

參考資料:command_block Mr素性測試+Prho分解小記OI-wiki 素數