【luogu P4718】【模板】Pollard-Rho算法(数学)(也含 Miller Rabin)

【模板】Pollard-Rho算法

题目链接:luogu P4718

题目大意

要你求一个数的最大质因子。

思路

Miller Rabin

首先,这种题肯定要判断一个数是不是素数。
但普通的方法会爆炸。
于是我们要用 Miller Rabin。

这是一个 \(O(logn)\) 判断质数的东西,运用了费马小定律和二次探测定理。

费马小定理

\(a^{p-1}\equiv1(\bmod\ p)\)
(条件是 \(p\) 是质数)

那我们可以根据这个来判断,随机几次 \(a\),看是否成立,如果不成立就说明不行。
但也会有一些数,它是合数,但你就是找不到 \(a\) 使得式子不满足,这些数叫做 Carmichael 数,就比如 \(561\)
这些数会使得判定出现误差,那我们考虑怎么搞。

二次探测定理

\(b^2\equiv1(\bmod\ p)\)\(p\) 为质数,则 \(p\) 一定可以被 \(b+1\)\(b-1\) 其中一个整除。
证明其实很简单:
\(b^2\equiv1(\bmod\ p)\)
\(b^2-1\equiv0(\bmod\ p)\)
\((b+1)(b-1)\equiv0(\bmod\ p)\)
然后就很显然了,\(p\) 是质数,所以就一定有倍数关系了。
然后再想想就发现 \(b=1/p-1\)
然后如果 \(b=1\) 那又可以继续二次探测!

那转回到费马小定理:
\(a^{p-1}\equiv1(\bmod\ p)\)
\(a^{{(\frac{p-1}{2})}^2}\equiv1(\bmod\ p)\)(前提是 \(p-1\) 是偶数)
然后就搞,然后就按这个来二次探测就可以了。

这么搞了之后,就很难会出错,大概 \(10^{10}\) 才会有一个出错,所以可以忽略不计。
然后不难看到要用点快速幂快速乘。

Pollard-Rho 算法

但你只能判断质数还不行,你还要分解质因数啊。
这时候就要用 Pollard-Rho 算法了,可以快速的将一个你确实是合数的数找到它的其中一个因子。

GCD

我们先从 GCD 来看,因为是合数,所以必然会有小于 \(\sqrt{n}\) 的因子。那我们考虑随机一个数,看它与 \(n\)\(\gcd\) 是否大于 \(1\),得到的 \(\gcd\) 就是一个因子。那这样选到的可能就是 \(\sqrt{n}\)
但在 \(10^{18}\) 面前,还是太弱小了,没有我们要的速度。

x^2+c

这是一个玄学的随机方法,可以使得你找到跟 \(n\) 有非 \(1\) 公约数的概率会增加。
这个方法是利用了生日悖论,它不符合生活经验,但是可以推导出来没问题的东西。

它的 \(x,c\) 一开始是随机的,然后让 \(y=x,x=x^2+c\),然后每次拿 \(y-x\) 去跟 \(p\)\(\gcd\)
它能搞到的概率十分之高,只能用玄学来形容。

判环

它这种方法很优秀,但是你因为生成过程中会取模,搞了多次一定会有环,那你如果这样都没找到,那你一直找下去都不会有结果,还不如重新随机 \(x,c\),重新开搞。

那你就要判环。
我们可以用倍增的思想,\(y\) 记住 \(x\) 位置,\(x\) 跑当前数量的一倍的次数,然后再记住,再跑。
因为有倍增,那 \(x,y\) 位置相同时,\(x\) 一定跑到了圈,而且不会多跑太多。

二次优化

然后你发现你每次判断都要求 \(\gcd\),这会让复杂度由 \(O(n^{\frac{1}{4}})\) 变成 \(O(n^{\frac{1}{4}}\log n)\)
虽然 \(\log n\) 很小,但毕竟是 \(10^{18}\),会让整个变慢一些。

你发现取模的数不是质数。然后有这么一个质数:
如果模数和被模的数都有一个公约数,那取模结果也是这个公约数的倍数。
那我们可以把中途的 \(y-x\) 都乘起来,那只要其中有一个跟 \(n\) 有非 \(1\) 共约数,你乘起来的数跟 \(n\) 就有非 \(1\) 公约数。
那我们可以多算几次 \(y-x\),再求一次 \(\gcd\),经过计算和测试,一般连续算 \(127\) 次再 \(\gcd\) 一次是最优的,跑的很快。

但你会发现会有一点小问题。
我们可能跑了没有 \(127\) 次,就出现了环。然后就导致还没有求出答案,就退了出来。
或者由于算法过度优秀(bushi,乘积乘着乘着直接变 \(n\) 倍数,取模直接是 \(0\),那没有必要干等着。

那处理第一个问题,我们可以用倍增来解决,在每个 \(i=j\) 要重新换位置的时候我们也做一次 \(\gcd\),因为是倍增的时候才 \(\gcd\) 所以影响不大。
那第二个简单的也做一个判断就可以了。

然后就好啦。

代码

#include<cstdio>
#include<cstdlib>
#define ll long long

using namespace std;

int T;
ll n, maxn_pr;

ll Abs(ll x) {
	return (x < 0) ? -x : x;
}

ll ksc(ll x, ll y, ll mo) {
	x %= mo; y %= mo;
	ll c = (long double)x * y / mo;
	long double re = x * y - c * mo;
	if (re < 0) re += mo;
		else if (re >= mo) re -= mo;
	return re;
}

ll ksm(ll x, ll y, ll mo) {
	ll re = 1;
	while (y) {
		if (y & 1) re = ksc(re, x, mo);
		x = ksc(x, x, mo);
		y >>= 1;
	}
	return re;
}

ll gcd(ll x, ll y) {
	ll tmp;
	while (y) {
		tmp = x; x = y; y = tmp % y;
	}
	return x;
}

bool mr(ll x, ll p) {
	if (ksm(x, p - 1, p) != 1) return 0;
	ll y = p - 1, z;
	while (!(y & 1)) {
		y >>= 1;
		z = ksm(x, y, p);
		if (z != 1 && z != p - 1) return 0;
		if (z == p - 1) return 1;
	}
	return 1;
}

bool prime(ll now) {//Miller Rabin 判是否是质数
	if (now < 2) return 0;
	if (now == 2 || now == 3 || now == 5 || now == 7 || now == 43) return 1;
	return mr(2, now) && mr(3, now) && mr(5, now) && mr(7, now) && mr(43, now);
}

ll rho(ll p) {
	ll x, y, z, c, g;
	int i, j;
	while (1) {
		x = y = rand() % p;
		z = 1;
		c = rand() % p;
		i = 0; j = 1;
		while (++i) {
			x = (ksc(x, x, p) + c) % p;
			z = ksc(z, Abs(y - x), p);
			if (x == y || !z) break;
			if (!(i % 127) || i == j) {//优化
				g = gcd(z, p);
				if (g > 1) return g;
				if (i == j) {
					y = x;
					j <<= 1;
				}
			}
		}
	}
}

void work(ll now) {
	if (now < 2) return ;
	if (now <= maxn_pr) return ;//小小剪枝
	if (prime(now)) {
		maxn_pr = now;
		return ;
	}
	ll l = rho(now);
	while (now % l == 0) now /= l;//要一直除,把这个因子都除掉
	work(l); work(now);//继续分解出来继续搞
}

int main() {
	srand(19491001);
	
	scanf("%d", &T);
	while (T--) {
		scanf("%lld", &n);
		maxn_pr = 0;
		work(n);
		if (maxn_pr == n) printf("Prime\n");
			else printf("%lld\n", maxn_pr); 
	}
	
	return 0;
}
posted @ 2021-08-24 09:03  あおいSakura  阅读(63)  评论(0编辑  收藏  举报