Pollard Rho大数质因数分解
基本思想
对于一个大整数 \(n\) ,随机取一个数是它的质因子的概率很小。但如果取两个数并且规定他们的差值为 \(n\) 的因数,那么这个概率就会提高。
对于满足 \(gcd(abs(x_1−x_2),n)>1\) 的 \(x_1\) 和 \(x_2\),\(gcd(abs(x_1−x_2),n)\) 就是 \(n\) 的一个因数,只需要判断它是否为素数,若为素数,则是 \(n\) 的质因数,否则递归此过程。其中判断素数就使用 Miller-Rabin 算法。\(x_1\)在区间 \([1,n]\) 中随机出来,而 \(x_2\) 则由 \(x[i]=(x[i-1]*x[i-1]%n+c)%n\) 推算出来,其中 \(c\)为任意给定值,事实证明,这样就是比较优的。
算法流程
我们先用 Miller-Rabin 判断当前的数是否是素数, 如果是则直接返回,否则找出这个数的一个因子(不一定是质因子),然后递归地分解这个因子和约掉这个因子的另一个因子。 然而在找出这个数的因子时,Pollard Rho 算法利用了一个随机化算法: 我们通过随机选取一个整数 \(a\),然后 \(b=a×a+c\) (\(c\)为任意整数)得到两个整数 \(a\) 和 \(b\), 设待分解的整数为 \(n\),那么循环计算 \(p=gcd(abs (a-b),n)\), 若 \(p\) 不为 \(1\) 或 \(a\) 和 \(b\) 出现循环则跳出, 否则需要将 \(b\) 的值赋给 \(a\),再次利用 \(b=a×a+c\) 计算新的 \(b\) 值。 若 \(a,b\) 出现循环,我们需要更换 \(c\) 值再次重新循环计算; 而若 \(1 < p < n\) ,则找到了一个因子 \(p\)。然后还有一个小小的优化,就是利用 \(Floyd\) 判环的原理,让 \(A\) 和 \(B\) 按照 \(B\) 的速度是 \(A\) 的速度的两倍从同一起点开始往前走 当 \(B\) 第一次赶上 \(A\) 时,\(B\) 已经走了至少一圈,利用这个优化可以快很多。
Miller_Rabin素数测试
基于费马小定理的一个拓展。
费马小定理:对于质数 \(p\),任意整数 \(a\),均满足:\(a^p≡a(mod\ p)\)
逆命题为:若任意正整数 \(n\) 满足 \(a^n ≡ a (mod\ n)\),则 \(n\) 为素数。(错误)
因此,假设我们要测试 \(n\) 是否为质数。我们可以随机选取一个数 \(a\),然后计算 \(a^{(n-1)} mod\ n\),如果结果不为 \(1\),我们可以断定 \(n\) 不是质数。即费马定理的逆命题。
Fermat 测试(费马测试):随机选取一个新的数 \(a\) 进行测试,如此反复多次,如果每次结果都是 \(1\),我们就假定 \(n\) 是质数。
但 Fermat 测试不一定是准确的,有可能出现把合数误判为质数的情况,如伪素数。
原先在费马小定理逆定理上的研究只考虑了 \(a=2\) 的情况,但是对于式子 \(a^(n-1) mod\ n\),取不同的 \(a\) 可能导致不同的结果:一个合数可能在 \(a=2\) 时通过了测试,但 \(a=3\) 时的计算结果却排除了素数的可能。于是,人们扩展了伪素数的定义,称满足 \(a^(n-1) mod\ n = 1\) 的合数 \(n\) 叫做以 \(a\) 为底的伪素数。前 \(10\) 亿个自然数中同时以 \(2\) 和 \(3\) 为底的伪素数只有 \(1272\) 个,这个数目不到仅仅以 \(2\) 为底的伪素数的 \(1/4\)。这告诉我们如果同时验证 \(a=2\) 和 \(a=3\) 两种情况,出错的概率降到了 \(0.0025%\) ,那么以此类推,测试的数越多,出错的可能性越小(不能降低到0)。
二次探测定理:
如果 \(p\) 是奇素数,则 \(x^2 ≡ 1(mod\ p)\) 的解为 \(x ≡ 1\) 或 \(x ≡ p - 1(mod\ p)\)
利用二次探测定理优化,那么算法就变成了:
尽可能提取因子 \(2\),把 \(n-1\) 表示成 \(d*2^t\)。然后令 \(x[0]=a^d mod\ p\),那么将 \(x[0]\) 平方 \(t\) 次就是 \((a^d)^{2^t}mod\ p\) 的值,我们设 \(x[i]\) 为 \(x[0]\) 平方 \(i\) 次的值,根据二次探测定理,若 \(x[i]\) 等于1,则 \(x[i−1]\) 等于 \(1\) 或 \(p-1\),不满足则 \(p\) 为合数。
时间复杂度:考虑常数后为 \(O(slog_3n)\)
模板代码
poj 1811
判断一个大数是否是一个素数,如果不是则输出其最小的质因子。
#include
#include
#include
#include
using namespace std;
const int N=1000;//数组大小
const int S=1000;//素数测试时的变换次数
typedef long long ll;
int tol;//分解的质因子的个数
ll factor[N];//存分解的质因子
ll gcd(ll a,ll b)
{
return b?gcd(b,a%b):a;
}
ll mul(ll a,ll b,ll p)
{
ll res=0;
while(b)
{
if(b&1) res=(res+a)%p;
a=(a+a)%p;
b>>=1;
}
return res;
}
ll power(ll a,ll b,ll p)
{
ll res=1;
while(b)
{
if(b&1) res=mul(res,a,p);
a=mul(a,a,p);
b>>=1;
}
return res;
}
//素数测试部分:
bool check(ll a,ll n,ll d,ll cnt)
{
ll res=power(a,d,n);//a^d%n
ll last=res;
for(int i=1;i<=cnt;i++)
{
res=mul(res,res,n);
if(res==1&&last!=1&&last!=n-1)//
return true;
last=res;//
}
if(res!=1) return true;//
return false;
}
bool Miller_Rabin(ll n)//判断一个大数是否是素数
{
if(n<2) return false;
if(n==2) return true;
if((n&1)==0) return false;
ll d=n-1,cnt=0;
while(d%2==0)//提取因子2
d>>=1,cnt++;
for(int i=1;i<=S;i++)
{
ll a=rand()%(n-1)+1;
if(check(a,n,d,cnt))
return false;//不是素数
}
return true;
}
//质因子分解部分:
ll pollard_rho(ll n,ll k)
{
ll u=2,v=1;
ll x=rand()%(n-1)+1;
ll y=x;
while(true)
{
x=(mul(x,x,n)+k)%n;
v++;
ll d=gcd((y-x+n)%n,n);
if(1