【算法简介】
- Pollar′sRho P o l l a r ′ s R h o 算法是一种用于分解质因数的算法。
- 对于一个被分解的数 N N ,假设 的最小的质因数为 p(p≠N) p ( p ≠ N ) ,那么 Pollar′sRho P o l l a r ′ s R h o 算法能够在 O(p–√∗α(N)) O ( p ∗ α ( N ) ) 的期望时间复杂度内将 N N 分解为两个不是 的数的乘积,其中 α(N) α ( N ) 是求解两个数的 gcd g c d 的时间复杂度,并且 Pollar′sRho P o l l a r ′ s R h o 算法几乎不需要额外的空间。
- 由于我们假定了 N N 不是质数,有 ,因此 Pollar′sRho P o l l a r ′ s R h o 算法产生一次有效的分解的期望时间复杂度为 O(N14∗α(N)) O ( N 1 4 ∗ α ( N ) ) 。
- 同时, Pollar′sRho P o l l a r ′ s R h o 算法还有一个不常用的优化,拟定一个参数 β β , Pollar′sRho P o l l a r ′ s R h o 算法产生一次有效的分解的期望时间复杂度可以被优化至 O(N14+N14∗α(N)β+β∗α(N)) O ( N 1 4 + N 1 4 ∗ α ( N ) β + β ∗ α ( N ) ) 。
【算法流程】
假设我们需要分解 N=p∗q N = p ∗ q ,其中 p p 是 的一个非平凡因子。
我们在模意义下生成一个近似随机的数列 {xn} { x n } , x1=2,xi=f(xi−1) mod N (i≥2) x 1 = 2 , x i = f ( x i − 1 ) m o d N ( i ≥ 2 ) ,其中 f(x) f ( x ) 为一个伪随机函数,可取 f(x)=x2+1 f ( x ) = x 2 + 1 。此时,另一个数列 {xn mod p} { x n m o d p } 同样客观存在着。由于这两个数列中的每一个数都由前一个数唯一确定,所以这两个数列一定都存在某个时刻,数列会进入一个循环,由 生日悖论 , {xn} { x n } 的循环节的出现位置期望为 O(N−−√) O ( N ) , {xn mod p} { x n m o d p } 的循环节的出现位置期望为 O(p–√) O ( p ) 。
假设存在两个位置 i,j i , j 使得 xi mod p=xj mod p x i m o d p = x j m o d p 且 xi≠xj x i ≠ x j ,那么我们取 gcd(xi−xj,N) g c d ( x i − x j , N ) ,就可以找到 N N 的一个非平凡因子。
那么我们不妨取 两个位置,计算 gcd(xi−x2i,N) g c d ( x i − x 2 i , N )
若 gcd(xi−x2i,N)=1 g c d ( x i − x 2 i , N ) = 1 ,那么说明 i,2i i , 2 i 不在任何数列的循环节的对应位置,继续枚举下一个 i i 。
若 ,那么说明 i,2i i , 2 i 同时在 {xn} { x n } 和 {xn mod p} { x n m o d p } 的循环节的对应位置,这表明我们无法通过枚举更大的 i i 来找到使得 且 xi≠x2i x i ≠ x 2 i 的 i i ,因此我们只能更换随机函数 ,重新进行算法。
否则,我们就找到了 N N 的一个非平凡因子 。
由于形如 f(x)=x2+a f ( x ) = x 2 + a 的随机函数只会生成在模 4 4 意义下为 和 a+1 a + 1 的数,因此当取 f(x)=x2+a f ( x ) = x 2 + a 的随机函数时,算法无法分解出 2 2 这个因子,需要特判。
我们期望需要枚举 个 i i 来分解出 的一个非平凡因子 gcd(xi−x2i,N) g c d ( x i − x 2 i , N ) ,因此。 Pollar′sRho P o l l a r ′ s R h o 算法能够在 O(p–√∗α(N)) O ( p ∗ α ( N ) ) 的期望时间复杂度内分解出 N N 的一个非平凡因子,其中 是求解两个数的 gcd g c d 的时间复杂度,即分解出 N N 的一个非平凡因子的期望时间复杂度为 。
注意到我们只有在 gcd(xi−x2i,N)=1 g c d ( x i − x 2 i , N ) = 1 的情况下才会继续运行算法,我们拟定一个参数 β β ,计算每 β β 个 xi−x2i x i − x 2 i 的乘积对 N N 取模的结果
若 gcd(prod,N)=1 g c d ( p r o d , N ) = 1 ,则说明这 β β 个 xi−x2i x i − x 2 i 与 N N 的 均为 1 1 。
若 ,那么我们就找到了 N N 的一个非平凡因子 。
否则,即 gcd(prod,N)=N g c d ( p r o d , N ) = N ,我们不知道此时我们是否需要重新进行算法,需要回到 β β 次操作之前,进行原本的 Pollar′sRho P o l l a r ′ s R h o 算法。
此时, Pollar′sRho P o l l a r ′ s R h o 算法产生一次有效的分解的期望时间复杂度被优化至了 O(N14+N14∗α(N)β+β∗α(N)) O ( N 1 4 + N 1 4 ∗ α ( N ) β + β ∗ α ( N ) ) 。
【代码】
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 100005;
const int p[9] = {2, 3, 5, 7, 11, 13, 17, 19, 23};
typedef long long ll;
typedef long double ld;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
template <typename T> void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
write(x);
puts("");
}
ll times(ll a, ll b, ll P) {
ll tmp = (ld) a * b / P;
return ((a * b - tmp * P) % P + P) % P;
}
ll mns(ll a, ll b) {
if (a >= b) return a - b;
else return b - a;
}
ll gcd(ll a, ll b) {
if (b == 0) return a;
else return gcd(b, a % b);
}
ll power(ll a, ll b, ll P) {
if (b == 0) return 1;
ll tmp = power(a, b / 2, P);
if (b % 2 == 0) return times(tmp, tmp, P);
else return times(a, times(tmp, tmp, P), P);
}
bool prime(ll n) {
for (int i = 0; i <= 8; i++) {
if (p[i] == n) return true;
else if (p[i] > n) return false;
ll tmp = power(p[i], n - 1, n), tnp = n - 1;
if (tmp != 1) return false;
while (tmp == 1 && tnp % 2 == 0) {
tnp /= 2;
tmp = power(p[i], tnp, n);
if (tmp == n - 1) break;
if (tmp != 1) return false;
}
}
return true;
}
ll brent(ll n, int steps, int add) {
if (n % 2 == 0) return 2;
ll x = 2, y = 2, d = 1;
while (true) {
ll tmpx = x, tmpy = y;
for (int i = 1; i <= steps; i++) {
x = times(x, x, n) + add;
if (x >= n) x -= n;
y = times(y, y, n) + add;
if (y >= n) y -= n;
y = times(y, y, n) + add;
if (y >= n) y -= n;
d = times(d, mns(x, y), n);
}
d = gcd(n, d);
if (d == 1) continue;
if (d != n) return d;
x = tmpx, y = tmpy;
for (int i = 1; i <= steps; i++) {
x = times(x, x, n) + add;
if (x >= n) x -= n;
y = times(y, y, n) + add;
if (y >= n) y -= n;
y = times(y, y, n) + add;
if (y >= n) y -= n;
d = gcd(n, mns(x, y));
if (d != 1) return d % n;
}
return 0;
}
}
ll work(ll n) {
if (prime(n)) return n; ll tmp = 0;
int steps = pow(n, 0.1), add = 1;
while (tmp == 0) tmp = brent(n, steps, add++);
return max(work(tmp), work(n / tmp));
}
int main() {
int T; read(T);
while (T--) {
ll n; read(n);
ll tmp = work(n);
if (tmp == n) printf("Prime\n");
else printf("%lld\n", tmp);
}
return 0;
}