学习笔记:Miller-Rabin 与 Pollard-Rho

Miller_Rabin

首先考虑方程 \(x^2 \equiv 1 \pmod n\)

可以写成 \((x + 1)(x - 1) = kn\)

\(n\) 为素数时,只可能 \(n \mid x + 1\)\(n \mid x - 1\),反之合数不满足。

得到结论:

在模素数意义下,一个数的平方等于 \(1\) 当且仅当这个数同余于 \(1\)\(-1\)

我们知道,对于素数 \(p\),有 \(x^{p - 1} \equiv 1 \pmod p\)

\(x^{\frac{p - 1}{2}}\) 呢?

\(p - 1\) 看做 \(2^k\cdot u\) 的形式,其中 \(u\) 是奇数。

依次计算 \(x^u, x^{2u}, \cdots, x^{p - 1}\),每个数都是前一个数的平方。

  • 如果最后一项不为 \(1\),则 \(p\) 一定不是质数。
  • 如果中间一项为 \(1\),而它的前一项不是 \(-1\),则 \(p\) 一定不是质数。

只要选取合适的底数进行检验,那么排除合数的概率很大。

取前 \(12\) 个质数作为底数,那么 \(64\) 位整数范围内都能保证正确性。

bool miller_rabin(ll n) {
	if(n <= 2 || !(n & 1)) {
		return n == 2;
	}
	static int base[] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37};
	ll u = n - 1, k = 0;
	while(u % 2 == 0) u >>= 1, ++ k;
	for(int x : base) {
		if(x % n == 0) continue;
		ll v = qpow(x, u, n);
		if(v == 1 || v == n - 1) continue;
		for(int i = 1; i <= k; ++ i) {
			ll lst = v;
			v = (__int128)v * v % n;
			if(v == 1) {
				if(lst != n - 1) return false;
				break;
			}	
		}
		if(v != 1) return false;
	}
	return true;
}

Pollard_Rho

随机生成均匀分布在 \([0, n)\) 的整数,那么生成一个之前出现过的数的期望次数是 \(O(\sqrt n)\) 的。

定义伪随机生成器 \(f(x) = (x^2 + c)\bmod n\)\(c\) 是一个常数。

指定一个 \(x_0\),生成序列 \(x_0, f(x_0), f(f(x_0)), \cdots\)

我们让前一项向后一项连边,得到下图:

形如字母 \(\rho\),也是该算法名字由来。

对于 \(x_i = x_j\),显然有 \(n \mid x_i - x_j\)

考虑 \(n\) 的一个因子 \(d\),一定存在 \(i, j\) 使得 \(d \mid x_i - x_j\)

如何理解呢?

把序列 \(a\) 整体模 \(d\),那么在新图中会出现一个新环,环上相同的两点 \(x_i, x_j\) 即满足条件。

也就是说,找因子的过程等价于找环的过程。

如何找环?

初始令 \(x = y = x_0\),每次让 \(x = f(x), y = f(f(x))\)\(y\) 的速度比 \(x\) 快,找到环后一定会追上 \(x\)

\(d = \gcd(x - y, n)\)\(x, y\) 为模 \(d\) 意义上的环上同一点。

如果 \(d > 1\),返回 \(d\),否则继续找环。

找到模 \(n\) 意义下的环是 \(O(n^{1/2})\) 的,找到模 \(d\) 意义下的环是 \(O(n^{1/4})\) 的。

pollard_rho 的朴素实现,理论复杂度 \(O(n^{1/4}\log n)\)

mt19937_64 mt(time(nullptr));

ll pollard_rho(ll n) {
	uniform_int_distribution<ll> u0(0, n - 1);
	ll c = u0(mt);
	auto f = [&](ll x) {return ((__int128)x * x + c) % n;};
	ll x = u0(mt), y = x;
	while(1) {
		x = f(x), y = f(f(y));
		ll d = __gcd(abs(x - y), n);
		if(d != 1) {
			return d;
		}
	}
}

gcd 调用过于频繁,衍生出倍增优化版本。

P4718 【模板】Pollard-Rho

#include<bits/stdc++.h>
using namespace std;

using ll = long long;


ll qpow(__int128 a, ll b, ll p) {
	ll ret = 1;
	while(b) {
		if(b & 1) ret = ret * a % p;
		b >>= 1;
		a = a * a % p;
	}
	return ret;
}


bool miller_rabin(ll n) {
	if(n <= 2 || !(n & 1)) {
		return n == 2;
	}	
	static int base[] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37};
	ll u = n - 1, k = 0;
	while(u % 2 == 0) u >>= 1, ++ k;
	for(int x : base) {
		if(x % n == 0) continue;
		ll v = qpow(x, u, n);
		if(v == 1 || v == n - 1) continue;
		for(int i = 1; i <= k; ++ i) {
			ll lst = v;
			v = (__int128)v * v % n;
			if(v == 1) {
				if(lst != n - 1) return false;
				break;
			}	
		}
		if(v != 1) return false;
	}
	return true;
}

mt19937_64 mt(time(nullptr));

ll pollard_rho(ll n) {
	uniform_int_distribution<ll> u0(0, n - 1);
	ll c = u0(mt);
	auto f = [&](ll x) {return ((__int128)x * x + c) % n;};
	ll x = 0, y = 0, s = 1;
	for(int k = 1;; k <<= 1, y = x, s = 1) {
		for(int i = 1; i <= k; ++ i) {
			x = f(x);
			s = (__int128)s * abs(x - y) % n;
			if(i % 127 == 0) {
				ll d = gcd(s, n);
				if(d > 1) return d;
			}
		}
		ll d = gcd(s, n);
		if(d > 1) return d;
	}
}

ll mx;
void get_factor(ll n) {
	if(miller_rabin(n)) {
		mx = max(mx, n);
		return;
	}
	if(n == 4) {
		mx = max(mx, 2ll);
		return;
	}
	ll p = pollard_rho(n);
	while(p == n) p = pollard_rho(n);
	get_factor(p);
	get_factor(n / p);
}


void solve() {
	ll x; cin >> x, mx = 0;
	get_factor(x);
	if(mx == x) {
		cout << "Prime\n";
		return;
	}
	cout << mx << '\n';
}

int main() {
	cin.tie(0)->sync_with_stdio(0);
	
	int T;
	cin >> T;
	
	while(T --) {
		solve();
	}
	return 0;
}
posted @ 2024-05-22 13:56  Lu_xZ  阅读(14)  评论(0编辑  收藏  举报