Miller-Rabin 素性测试 & Pollard-Rho 算法 学习笔记

Miller-Rabin 素性测试 & Pollard-Rho 算法 学习笔记

素性测试

判断一个数是否是素数。

试除法

一种确定性算法。枚举 \([1,\sqrt n]\) 的每个数检验能否除 \(n\)。复杂度 \(O(\sqrt n)\)

Fermat 素性测试

简单的概率性素数检验。根据费马小定理:

对于质数 \(p\)\(\gcd(a,p)=1\),有 \(a^{p-1}\equiv 1 \pmod p\)

我们的思路是,不断选取 \([2,n-1]\) 中的数 \(a\),判断 \(a^{n-1}\equiv1\pmod n\) 是否总成立。

bool is_prime(int n) {
    if(n<3) return n==2;
    const int test_time=8;
  // test_time 为测试次数,建议设为不小于 8
  // 的整数以保证正确率,但也不宜过大,否则会影响效率
    for(int i=1;i<=test_time;++i) {
        int a=rnd(2,n-1);
        if(pow1(a,n-1,n) != 1) return false;
    }
    return 0;
}

然而存在合数使得对于 \([2,n-1]\) 的数 \(a\) 都有 \(a^{n-1}\equiv 1\pmod n\),这样的数称为卡迈克尔数,例如 561 就是最小的卡迈克尔数。所以接下来我们考虑 Fermat 素性测试的优化。

Miller–Rabin 素性测试

二次探测定理:对于质数 \(p\)\(x^2\equiv 1\pmod p\) 的解只有 \(x\equiv 1\pmod p\)\(x\equiv p-1 \pmod p\)。证明只需对方程移项,用平方差公式可得 \((x+1)(x-1)\equiv 0\pmod p\)

依旧选取 \(a\),将 \(a^{n-1}\equiv 1\pmod n\) 中的 \(n\) 拆成 \(u\times 2^t\)。先求 \(a^u\bmod n\),之后对其 \(t\) 次平方,如果发现出现为非 \(n-1\)\(1\) 的平方根即可判断其不是素数。

实现上,先判断 \(a^u\) 是否等于 \(1\),等于 \(1\) 则此轮结束。否则做 \(t\) 次平方,某一次平方前如果 \(a=n-1\)\(a\) 之后总是 \(1\),此轮结束。否则 \(t\) 次平方 \(a\) 没有变成过 \(n-1\),那么考虑第一个 \(1\) 之前的 \(a\) 一定是 \(n\) 不满足条件的根,或者 \(a^{p-1}\) 就不是 \(1\),所以 \(n\) 不是质数。

可以证明,当 \(n<2^{64}\) 时,把 \(A=[2,3,5,7,11,13,17,19,23,29,31,37]\) 这 12 个 37 以内的质数依次作为 \(a\) 时素性测试一定是正确的。实现时要把 \(A_i\bmod n\) 作为基底 \(a\),且要满足 \(a\ne\pm1,0\)

计算量是 \(O(|A|\log n)\)

int A[12]={2,3,5,7,11,13,17,19,23,29,31,37};
ll pow1(ll x,ll y,ll n) {
	ll res=1;
	for(;y;y>>=1,x=(it128)x*x%n) if(y&1) res=(it128)res*x%n;
	return res;
}
bool is_prime(ll n) {
	if(n<3||n%2==0) return n==2;
	if(n%3==0) return n==3;
	ll u=n-1,t=0;
	while(u%2==0) u/=2,++t;
	fu(i,0,12) {
		ll _a=A[i]%n;
		if(_a<=1||_a==n-1) continue;
		ll a=pow1(_a,u,n);
		if(a==1) continue;
		int j=0;
		while(j<t) {
			if(a==n-1) break;
			a=(it128)a*a%n;
			++j;
		}
		if(j==t) return 0;
	}
	return 1;
}

分解质因数

\(n\) 的所有质因子。

试除法

复杂度 \(O(\sqrt n)\),预处理质数后可以做到 \(O(\frac {\sqrt n}{\ln n})\)

随机法

以下的算法考虑找出 \(n\) 的因数 \(m\) 并递归处理 \(m,\frac nm\) 直到 \(n\) 为素数。

考虑每次随机一些数,将它们作差,如果差 \(d\) 满足 \(\gcd(d,n)>1\),则 \((d,n)\) 是因数。

最坏情况下,当 \(n=p^2\) 时,在 \([1,n-1]\) 生成随机数,期望只要随机 \(O(\sqrt p)\) 个数就有两个数 \(i,j\) 使得 \(|i-j|\equiv 0 \pmod p\)。然而两两之间作差使得复杂度还是 \(O(\sqrt n)\) 而且还要额外乘上 \(\gcd\) 的复杂度。

Pollard-Rho

考虑另一种随机方式,我们定义一个伪随机数生成方式,\(f(x)=(x^2+c)\bmod n\),其中 \(c\) 是一个常数。那么初始化 \(x=0\),将 \(x\)\(f(x)\) 连边会形成 \(\rho\) 的形状,即环加链。考虑到 \(f(x)\) 有性质:对于 \(|i-j|\equiv 0 \pmod p\),有 \(|f(i)-f(j)|=|i^2-j^2|=|(i+j)(i-j)|\equiv0\pmod p\),这意味着对于环上一种距离的点对若满足条件,则所有距离相同的点对都满足条件。

考虑初始让 \(a=0,b=0\),随机一个 \(c\),每次让 \(a\) 走一步,\(b\) 走两步,即 \(a\gets f(a),b\gets f(f(b))\),每次判断是否 \(\gcd(|a-b|,n)>1\)。这样 \(a,b\) 每走一次都在检查一个新的距离,如果 \(a,b\) 再次相遇了则这个 \(\rho\) 里没有得到因子,再随机新的 \(c\) 重复这个过程。

如上所述,假设 \(f(x)\) 均匀随机,分解质因子的复杂度为 \(O(n^{0.25}\times \log n)\),其中 $\log $ 为 \(\gcd\) 的复杂度。实现如下(函数返回一个非平凡因子):

mt19937_64 rng(chrono::system_clock::now().time_since_epoch().count());
ll rnd(ll l,ll r) {
	return rng()%(r-l+1)+l;
}
ll pollard_rho(ll n) {
	if(n==4) return 2; // 注意需要特判,不然会进入 1 和 2 的循环然后永远找不到
	if(is_prime(n)) return n;
	while(1) {
		ll c=rnd(1,n-1);
		auto f=[&](ll x) {return ((it128)x*x+c)%n;};
		ll a=f(0),b=f(a);
		while(a!=b) {
			ll d=__gcd(abs(a-b),n);
			if(d>1) return d;
			a=f(a),b=f(f(b));
		}
	}
}

模版题

posted @ 2025-08-13 20:54  dengchengyu  阅读(27)  评论(0)    收藏  举报