pollard_rho算法是用来解决质因数分解问题的。
我们知道,朴素的质因数分解是
O
(
n
)
O(\sqrt{n})
O(n)的,但是如果
n
n
n很大的话应该怎么办呢?这时候就要用到pollard_rho了。
pollard_rho也是一种基于随机的算法,它的思路是先用miller_rabin来判断当前数是否已经是素数了,如果是的话记录并返回。如果不是,我们设要分解的数为
n
n
n,那么我们考虑去找一个当前数的因数
p
p
p,找到之后再分别对
p
p
p和
n
/
p
n/p
n/p分解质因数,有一点类似分治但是不是分治。
那么我们现在的问题是如何快速寻找一个数的一个因数?这个算法用到了生日悖论,不了解的话可以自行搜索一下,我这里不对其赘述。根据生日悖论,我们引申出如果我们随机生成一个
1
1
1到
n
n
n的数,它是要分解数
n
n
n的因数的概率很小,但是如果我们随机生成两个
1
1
1到
n
n
n的数,它们的差的绝对值是
n
n
n的概率就会变大。我们基于这种原理来找
n
n
n的因子。
对于我们这两个数,我们不能每次都随机生成。我们是这样得来每次的两个数的,一开始,先随机生成一个
x
x
x和一个
c
c
c,每次让
x
=
x
∗
x
+
c
(
m
o
d
n
)
x=x*x+c(mod\ n)
x=x∗x+c(mod n),另一个数
y
y
y一开始的值是
x
x
x,每进行
2
k
2^k
2k次让
y
y
y记为现在的
x
x
x。我们发现,
x
=
x
∗
x
+
c
(
m
o
d
n
)
x=x*x+c(mod\ n)
x=x∗x+c(mod n)这个过程是会出现循环的,具体画出来会像一个希腊字母ρ(rho)。一旦出现循环我们还是没有找到可以分解的因数就说明这次随机失败了。我们为了记录什么时候出现了循环,每隔
2
k
2^k
2k把y记录成当前
x
x
x,如果在接下来的
2
k
2^k
2k次操作中
x
x
x和
y
y
y相等了,就意味着出现了循环。所以
y
y
y的作用一来是当那随机的第二个数,二来是记录是否出现循环。
最后来分析一下复杂度(某位神犇给本蒻讲的):首先因为这是个随机算法,所以它只有期望复杂度。根据生日悖论,我们分解出每个质因数期望随机出
质
因
数
大
小
\sqrt{质因数大小}
质因数大小次即可,而最坏情况最小质因数大小是
要
分
解
的
数
\sqrt{要分解的数}
要分解的数的,所以最后复杂度应该是
O
(
n
1
4
)
O(n^{\frac{1}{4}})
O(n41)的,
n
n
n是要分解的数。实际上,应该在质因数越多的时候质因数大小越小,所以会跑得快,所以并不需要在复杂度上乘期望质因子个数,这个的影响应该是个常数级别的。
最后附一下代码:
#include <iostream>
#include <ctime>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <cstdlib>
using namespace std;
int t,s=20,cnt;
long long fac[1001];
long long ksc(long long x,long long y,long long mod)
{
long long res=0;
while(y)
{
if(y&1)
res=(res+x)%mod;
x=(x<<1)%mod;
y>>=1;
}
return res;
}
long long ksm(long long x,long long y,long long mod)
{
long long res=1;
while(y)
{
if(y&1)
res=ksc(res,x,mod);
x=ksc(x,x,mod);
y>>=1;
}
return res;
}
int miller_rabin(long long n)
{
if(n==2)
return 1;
if(n<2||!(n%2))
return 0;
long long u,pre,x;
int k=0;
u=n-1;
while(!(u&1))
{
++k;
u>>=1;
}
for(int i=1;i<=s;++i)
{
x=rand()%(n-2)+2;
x=ksm(x,u,n);
pre=x;
for(int j=1;j<=k;++j)
{
x=ksc(x,x,n);
if(x==1&&pre!=1&&pre!=n-1)
return 0;
pre=x;
}
if(x!=1)
return 0;
}
return 1;
}
long long gcd(long long a,long long b)//注意与一般的gcd不一样
{
if (a==0) return 1;//pollard_rho的需要
if (a<0) return gcd(-a,b);//可能有负数
while (b){
long long t=a%b; a=b; b=t;
}
return a;
}
long long pollard_rho(long long n,long long c)//找因子
{
long long i=1,k=2;//用来判断是否形成了环
long long xx=rand()%n,y=xx;
while(1)
{
i++;
xx=(ksc(xx,xx,n)+c)%n;
long long d=gcd(y-xx,n);
if(1<d&&d<n)//找到一个因数
return d;
if(y==xx)//出现循环,那么这次寻找失败
return n;
if(i==k)//相当于每次找连续k这么多次取模有没有得到相同余数
{
y=xx;
k<<=1;
}
}
}
void find(long long n)//通过找因数来找质因子
{
if(miller_rabin(n))
{
fac[++cnt]=n;//记录质因子
return;
}
long long p=n;
while(p>=n)
p=pollard_rho(p,rand()%(n-1)+1);//如果转了一圈还是p,则继续while循环
//p是当前找到的一个因数(不一定是质因数),再分别找p和n/p的质因数
find(p);
find(n/p);
}
int main()
{
srand(time(0)+19260817);
scanf("%d",&t);
while(t--)
{
long long x;
scanf("%lld",&x);
if(miller_rabin(x))
{
printf("Prime\n");
continue;
}
cnt=0;
find(x);
sort(fac+1,fac+cnt+1);
printf("%lld\n",fac[1]);
}
return 0;
}
题单:
洛谷1075质因数分解
HDU3864 D_num
洛谷4358密钥破解