问题引入
给定一个正整数 ,请找到其一个因子。
朴素算法
要找到大整数的一个因子,最朴素的想法是试除法。也就是这样:
int Find_Factor(int x) {
for(int i = 2; i * i <= N; i++)
if(n % i == 0)
return i;
return n;
}
这样的时间复杂度是 的,不能够处理 及以上的大数。
基于随机的算法
我们可以先用 Miller-Rabbin 算法检测质数,如果这个数不是质数,每次从 中随机一个数字进行判断,也就是这样:
int RandInt(int l , int r) {
static mt19937 Rand(time(0));
uniform_int_distribution<int> dis(l, r);
return dis(Rand);
}
int Find_Factor(int n) {
if(Miller_Rabbin(n))
return n;
int x;
do {
x = RandInt(2 , n - 1);
} while(n % x);
return x;
}
上述算法最差时间复杂度为
这个糟糕的随机算法可以进行改进:
int RandInt(int l , int r) {
static mt19937 Rand(time(0));
uniform_int_distribution<int> dis(l, r);
return dis(Rand);
}
int Find_Factor(int n) {
if(Miller_Rabbin(n))
return n;
int x , y;
do {
x = RandInt(2 , n - 1);
y = __gcd(n , x);
} while(y == 1);
return y;
}
分析最差时间复杂度:当 为 ( 为质数),显然当随机数 取 时都可以找到 ,所以时间复杂度为 的,连暴力都打不过,但这是 Pollard-Rho 算法的基础。
生日悖论
这个悖论是 Pollard-Rho 算法的前置知识。
在一个房间中至少有多少人可以使得同一天出生的人出生的概率为 ?
假设一年有 天,房间里共有 个人,编号为 ,假设生日随机分布。
设 个人生日不相同为事件 ,则有:
同一天出生的人出生的概率为 ,当 时显然有 。
使用 Mathemaica 计算出来 时 。
实际上可以使用随机变量指示器进行近似分析:
设第 个人和第 个人同时在第 天出生为事件 ,有
由于一年有 天,所以有
设随机变量指示器 ,定义:
那么有:
设 为两个人在同一天出生的人的无序对数,即 则有:
由数学期望的线性性质得:
解得当 时,房间中有 人就可以期望有两人生日相同。
于是可以这样优化:
原来从 中随机出我们想要的数字 的概率为 ,现在我们随机两个数 使得 的概率大约为 ,概率扩大了两倍左右。
伪随机数序列
Pollard-Rho 使用一种特别的伪随机数生成器来生成 间的伪随机数序列:设序列第一个数为 ,,其中 为可以自行指定的常数,则 为一个伪随机数序列。
显然的,每个数都由前一个数字决定,以生成的数又是有限的,那么迟早会进入循环,而且大概率为混循环,所以生成的序列类似一个 形,故名 算法。
遇到环后我们接下来做的随机都没有意义了,所以需要更换 重新开始。
Floyd 判环算法
设置两个变量 ,每次判断是否有 ,如果没有,就令 ,。因为 跑的更快,那么它们最后会相遇,这时候就换一个 重新生成随机数。
这个伪随机数生成器有一个性质:如果 ,那么有
由此可得,只要环上距离为 的两个数满足条件,那么所有距离为 的数都满足条件。在Floyd判环的过程中,每次移动都相当于在检查一个新的距离 ,这样就不需要进行两两比较了。
这个算法的复杂度依赖于这个伪随机数生成器的随机程度,还没有被严格证明。如果它是足够随机的,那么期望复杂度显然是 。
ll RandInt(ll l , ll r) {
static mt19937 Rand(time(0));
uniform_int_distribution<ll> dis(l, r);
return dis(Rand);
}
ll Pollard_Rho(ll n) {
if(n == 4) {
return 2;
}
if(Miller_Rabin(n)) {
return n;
}
while(true) {
ll c = RandInt(1 , n - 1);
auto f = [=](ll x) {
return ((__int128)x * x + c) % n;
};
ll t = f(0) , r = f(f(0));
while(t != r) {
ll d = __gcd(abs(t - r) , n);
if(d > 1)
return d;
t = f(t);
r = f(f(r));
}
}
}
倍增优化
由于 的运算会花费 的时间,从而使我们的时间复杂度挂上了 ,这使人非常不爽,现在我们想办法去掉这个 。
我们使用 乘法累计 来减少求 的次数。如果 ,那么有 ,并且有 。
我们每过一段时间对这些差值进行 运算,设 ,如果过程中存在 测表示分解失败, 是质数。每隔 个数,计算是否满足 。此处 ,可以更具需要自行调节。
typedef long long ll;
typedef __int128 lll;
ll RandInt(ll l , ll r) {
static mt19937 Rand(time(0));
uniform_int_distribution<ll> dis(l, r);
return dis(Rand);
}
ll Pollard_Rho(ll n) {
if(Miller_Rabin(n)) {
return n;
}
ll s = 0 , t = 0;
ll c = RandInt(1 , n - 1);
int step = 0 , goal = 1;
ll value = 1;
auto f = [=](ll x) {
return ((lll)x * x + c) % n;
};
for(goal = 1;; goal <<= 1, s = t , value = 1) {
for(step = 1; step <= goal; step++) {
t = f(t);
value = ((lll)value * abs(t - s)) % n;
if(step % 127 == 0) {
ll d = __gcd(value , n);
if(d > 1) return d;
}
}
ll d = __gcd(value , n);
if(d > 1) return d;
}
}
【模板】Pollard-Rho算法
思路
对于一个数 ,如果用 Miller_Rabin 判断出来他是质数,就可以直接返回。
否则找到它的一个真因子 ,把 除去所有的 因子,再递归分解 和 ,其中使用 Miller_Rabin 判断是否存在质因子,并打擂台取最大值。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef __int128 lll;
ll RandInt(ll l , ll r) {
static mt19937 Rand(time(0));
uniform_int_distribution<ll> dis(l, r);
return dis(Rand);
}
ll Quick_Power(ll a , ll b , ll p) {
ll res = 1;
while(b) {
if(b & 1) res = (lll)res * a % p;
a = (lll)a * a % p;
b >>= 1;
}
return res;
}
bool Miller_Rabin(ll n) {
if(n < 3 || n % 2 == 0) return n == 2;
ll a = n - 1 , b = 0;
while(a % 2 == 0) {
a /= 2;
b++;
}
for(int i = 1 , j; i <= 10; i++) {
ll x = RandInt(2 , n - 1);
ll v = Quick_Power(x , a , n);
if(v == 1) continue;
for(j = 0; j < b; j++) {
if(v == n - 1) break;
v = (lll) v * v % n;
}
if(j == b) return 0;
}
return 1;
}
ll Pollard_Rho(ll n) {
ll s = 0 , t = 0;
ll c = RandInt(1 , n - 1);
int step = 0 , goal = 1;
ll value = 1;
auto f = [=](ll x) {
return ((lll)x * x + c) % n;
};
for(goal = 1;; goal <<= 1, s = t , value = 1) {
for(step = 1; step <= goal; step++) {
t = f(t);
value = ((lll)value * abs(t - s)) % n;
if(step % 127 == 0) {
ll d = __gcd(value , n);
if(d > 1) return d;
}
}
ll d = __gcd(value , n);
if(d > 1) return d;
}
}
ll Ans;
void Fac(ll n) {
if(n <= Ans || n < 2) return;
if(Miller_Rabin(n)) {
Ans = max(Ans , n);
return;
}
ll p = n;
while(p == n) p = Pollard_Rho(n);
while((n % p) == 0) n /= p;
Fac(n);
Fac(p);
}
ll T , N;
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> T;
while(T--) {
Ans = 0;
cin >> N;
Fac(N);
if(Ans == N) {
cout << "Prime" << endl;
}
else {
cout << Ans << endl;
}
}
return 0;
}