$PollardRho$ 算法及其优化详解

\(PollardRho\) 算法总结:

Pollard Rho是一个非常玄学的算法,用于在\(O(n^{1/4})\)的期望时间复杂度内计算合数n的某个非平凡因子(除了1和它本身以外能整除它的数)。事实上算法导论给出的是\(O(\sqrt p)\)\(p\)\(n\)的某个最小因子,满足\(p\)\(n/p\)互质。但是这些都是期望,未必符合实际。但事实上Pollard Rho算法在实际环境中运行的相当不错。

一些与\(PollardRho\) 算法经常一起考的东西:

  1. 快速乘
  2. Miller Rabin判质数

\(PollardRho\) 算法:

对于一个比较小的数(\(n\leq10^9\)),我们直接暴力就行.但太大了我们就必须考虑一下随机算法了。

对于一个非常大的合数\(n\leq 10^{18}\)(如果可能是质数,我们可以用Miller Rabin判一下)我们要求 \(n\) 的某一个非平凡因子,如果 \(n\) 的质因子很多(就是约数很多)我们也可以暴力随机弄一弄,但如果是一些(像由两个差不多的而且很大的质数乘得的 \(n\) )它的非平凡因子就只有两个而且 \(n\) 本身还很大,此时如果我们随机的话复杂度\(O(n)\),这个太难接受了,所以我们想办法优化一下。

1.我们求\(gcd\)

如果我们直接随机求 \(n\) 的某一个约数复杂度很高,我们可以考虑求一下 \(gcd\) 。因为我们可以保证一个合数它绝对存在一个质因子小于\(\sqrt n\) ,所以在 \(n\) 就存在至少\(\sqrt n\) 个数与 \(n\) 有大于一的公约数。于是我们随机的可能性就提高到了\(O(\sqrt n)\)

2. 玄学随机法:\(x^2+c \quad _{x=rand(),c=rand()}\)

其实网上大多都叫这种方法为生日悖论,有兴趣的可以去看看,但这个其实是可以通过数学概率证明的。但博主觉得这个方法跟我们的\(PollardRho\) 没有关联。在1中我们用gcd的方案,现在我们考虑有没有办法能够快速的找到这些与 \(n\) 有公约数的数(或者说找到一种随机生成方法,使随机生成这类我们需要的数的概率变大一点)。

这个随机生成方法最终被\(Pollard\)研发出来了:就是上面那个式子! 我们定义\((y=x\quad x=x*x+c \quad g=gcd(y-x,p))\) 为一次操作,我们发现 \(g>1\) (就是找到我们需要的数)的几率出奇的高,根据国际上大佬的疯狂测试,在 \(n\) 中找到一对 \(y-x\) 使两者有公约数的概率接近\(n^{\frac{1}{4}}\) ,不得不说太玄学又太优秀了!

但是这种随机生成方法虽然优秀,也有一些需要注意之处,比如有可能会生成一个环,并不断在这个环上生成以前生成过一次的数,所以我们必须写点东西来判环:

  1. 我们可以让y根据生成公式以比x快两倍的速度向后生成,这样当y再次与x相等时,x一定跑完了一个圈且没重复跑多少!
  2. 我们可以用倍增的思想,让y记住x的位置,然后x再跑当前跑过次数的一倍的次数。这样不断让y记住x的位置,x再往下跑,因为倍增所以当x跑到y时,已经跑完一个圈,并且也不会多跑太多(这个必须好好想一想,也可以看看代码如何实现的)(因为这种方法会比第一种用的更多,因为它在\(PollardRho\) 二次优化时还可以起到第二个作用)(博主这里就只给第二种的代码吧)
inline ll rho(ll n){
    ll x,y,c,g; rg i,j;
    x=y=rand(); c=rand();//初始化
    i=0,j=1;//倍增初始化
    while(++i){
        x=(ksc(x,x,n)+c)%n;//x只跑一次
        if(x==y)break;//跑完了一个环
        g=gcd(Abs(y-x),n);//求gcd(注意绝对值)
        if(g>1)return g;
        if(i==j)y=x,j<<=1;//倍增的实现
    }
}

\(PollardRho\) 算法的二次优化:

我们发现其实\(PollardRho\) 算法其实复杂度不止\(O(n^{\frac{1}{4}})\),它每一次操作都会求一次\(gcd\),所以复杂度会变成\(O(n^{\frac{1}{4}}*log)\) 我们发现这个 \(log\) 实在有些拖慢速度(虽然实际上也很快了)。于是一个很奇妙的优化诞生了!

二次优化:我们发现我们在每一次生成操作中,乘法之后所模的数就是我们的\(n\),而我们要求的就是\(n\)的某一个约数!也就是说我们现在的模数并不是一个质数!而根据取模的性质:如果模数和被模的数都含有一个公约数,那么这次模运算的结果必然也会是这个公约数的倍数!!!所以如果我们将若干个\((y-x)\) 乘到一起,因为中途模的是 \(n\) ,所以如果我的若干个\((y-x)\)中有一个与n有公约数,最后的结果定然也会含有这个公约数!所以我们完全可以多算几次\((y-x)\)的乘积在来求\(gcd\) (一般连续算127次再求一次gcd(这个应该有大佬测试过了)),这样我们的复杂度就能降一个\(log\) 级别,跑的飞快!

对原算法的一些影响:任何一个优化都是要考虑其对原算法的影响的。这个优化也会有一些影响:首先,因为我们会等大概127次后再去gcd,然而我们有可能在生成时碰上一个环,我们有可能还没生成127次就跳出这个环了,这样就无法得出答案;其次,我们的 \(PollardRho\) 算法实在太玄学优秀了,可能我们跑127次之后,所有\((y-x)\) 的乘积就变成了n的倍数(模\(n\) 意义下得到 \(0\) ),所以我们不能完全就只呆板的等127次在取模。

那怎么办呢?(博主在上文就已经提到过了:倍增时我们的 \(i\)\(j\) 会在二次优化时起到另一种作用。)没错!就是倍增!我们可以用倍增,分别在生成(1次,2次,4次,8次,16次,32次......)之后进行 \(gcd\) !这样就可以完全避免上述的两个影响,而且我们是倍增来gcd的这对我们的复杂度影响不大!!!!!然后我们看代码吧!

inline ll rho(ll p){//求出p的非平凡因子
    ll x,y,z,c,g; rg i,j;//先摆出来(z用来存(y-x)的乘积)
    while(1){//保证一定求出一个因子来
        y=x=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);//我们将每一次的(y-x)都累乘起来
            if(x==y||!z)break;//如果跑完了环就再换一组试试(注意当z=0时,继续下去是没意义的)
            if(!(i%127)||i==j){//我们不仅在等127次之后gcd我们还会倍增的来gcd
                g=gcd(z,p);
                if(g>1)return g;
                if(i==j)y=x,j<<=1;//维护倍增正确性,并判环(一箭双雕)
            }
        }
    }
}

然后是两道道例题,都有点难度的。

\(code1:\)

博主给出的样例代码是对应多组数据的,对于每个数字检验是否是质数,是质数就输出Prime;如果不是质数,输出它最大的质因子是哪个。对应洛谷P4718 【模板】Pollard-Rho算法

#include<iostream>
#include<cstdio>
#include<iomanip>
#include<algorithm>
#include<cstring>
#include<cstdlib>
#include<ctime>
#include<queue>
#include<vector>
#include<stack>
#include<map>
#include<set>

#define ull unsigned long long
#define lb long double
#define ll long long
#define rg register int

using namespace std;

int n;
ll ans,m;

inline ll qr(){//快读
    char ch;
    while((ch=getchar())<'0'||ch>'9');
    ll res=ch^48;
    while((ch=getchar())>='0'&&ch<='9')
        res=res*10+(ch^48);
    return res;
}

inline ll Abs(ll x){return x<0?-x:x;}//取绝对值
inline ll gcd(ll x,ll y){//非递归求gcd
    ll z;
    while(y){z=x;x=y;y=z%y;}
    return x;
}

inline ll ksc(ull x,ull y,ll p){//O(1)快速乘(防爆long long)
    return (x*y-(ull)((lb)x/p*y)*p+p)%p;
}

inline ll ksm(ll x,ll y,ll p){//快速幂
    ll res=1;
    while(y){
        if(y&1)res=ksc(res,x,p);
        x=ksc(x,x,p); y>>=1;
    }return res;
}

inline bool mr(ll x,ll p){//mille rabin判质数
    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;
}

inline bool prime(ll x){ if(x<2)return 0;//mille rabin判质数
    if(x==2||x==3||x==5||x==7||x==43) return 1;
    return mr(2,x)&&mr(3,x)&&mr(5,x)&&mr(7,x)&&mr(43,x);
}

inline ll rho(ll p){//求出p的非平凡因子
    ll x,y,z,c,g; rg i,j;//先摆出来(z用来存(y-x)的乘积)
    while(1){//保证一定求出一个因子来
        y=x=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);//我们将每一次的(y-x)都累乘起来
            if(x==y||!z)break;//如果跑完了环就再换一组试试(注意当z=0时,继续下去是没意义的)
            if(!(i%127)||i==j){//我们不仅在等127次之后gcd我们还会倍增的来gcd
                g=gcd(z,p);
                if(g>1)return g;
                if(i==j)y=x,j<<=1;//维护倍增正确性,并判环(一箭双雕)
            }
        }
    }
}

inline void prho(ll p){//不断的找他的质因子
    if(p<=ans)return ;//最优性剪纸
    if(prime(p)){ans=p;return;}
    ll pi=rho(p);//我们一次必定能求的出一个因子,所以不用while
    while(p%pi==0)p/=pi;//记得要除尽
    return prho(pi),prho(p);//分开继续分解质因数
}

int main(){
    n=qr(); srand(time(0));//随机数生成必备!!!
    for(rg i=1;i<=n;++i){
        ans=1; prho((m=qr()));
        if(ans==m)puts("Prime");
        else printf("%lld\n",ans);
    }
    return 0;
}

\(code2:\)

对应:杭电3864 \(D\_num\)

根据这道题的答案性质,我们知道这个数在除了1以外还有三个约数,仔细想一下不难发现这类数最多只能有两个质因子,而这两个质因子分别就是它的两个约数,而第三个约数自然就是它自己了!(当然还有可能出现质数的三次方这类特殊的数,所以我们要特判一下)

所以我们的思路就是,先求出 \(n\) 的一个小于它的约数,然后我们可以用这个约数算出 \(n\) 的另一个约数,然后我们判一下它是不是质数的三次方这类特殊的数(是就可以直接输出答案了),不是的话我们判断这两个约数是否分别是不同的质因子,是就能输出了,不是就说明这不是\(D\_num\)

#include<iostream>
#include<cstdio>
#include<iomanip>
#include<algorithm>
#include<cstring>
#include<cstdlib>
#include<ctime>
#include<queue>
#include<stack>
#include<vector>
#include<map>
#include<set>

#define ll long long
#define db double
#define rg register int
#define cut {puts("is not a D_num");continue;}//这个可以看一看

using namespace std;

ll n;

inline ll qr(){
    char ch;
    while((ch=getchar())<'0'||ch>'9');
    ll res=ch^48;
    while((ch=getchar())>='0'&&ch<='9')
        res=res*10+(ch^48);
    return res;
}

inline ll Abs(ll x){return x<0?-x:x;}
inline ll gcd(ll x,ll y){
    ll z;
    while(y){z=x,x=y,y=z%y;}
    return x;
}

inline ll ksm(ll x,ll y,ll p){
    ll res=1;
    while(y){
        if(y&1)res=(__int128)res*x%p;
        x=(__int128)x*x%p; y>>=1;
    }return res;
}

inline 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;
}

inline bool prime(ll p){ if(p<2)return 0;
    if(p==2||p==3||p==5||p==7||p==43)return 1;
    return mr(2,p)&&mr(3,p)&&mr(5,p)&&mr(7,p)&&mr(43,p);    
}

inline ll rho(ll p){
    ll x,y,z,c,g; rg i,j;
    while(1){
        y=x=rand()%p;
        z=1,c=rand()%p;
        i=0,j=1;
        while(++i){
            x=((__int128)x*x+c)%p;
            z=(__int128)z*Abs(y-x)%p;
            if(x==y)break;
            if(i%72||i==j){
                g=gcd(z,p);
                if(g>1&&g!=p)return g;
                if(i==j)y=x,j<<=1;
            }
        }
    }
}

int main(){
//freopen(".in","r",stdin);
//freopen(".out","w",stdout);
    srand(time(0)); ll p,q;
    while(scanf("%lld",&n)!=EOF){
        if(prime(n)||n<=2)cut;
        p=rho(n); q=n/p;
        if((__int128)p*p==q||(__int128)q*q==p)//这题很卡细节的
        {printf("%lld %lld %lld\n",p,q,n);continue;}
        if(!prime(p)||!prime(q)||p==q)cut;//注意两个相等也不行
        printf("%lld %lld %lld\n",min(p,q),max(p,q),n);
    }
    return 0;
}

转载于:https://www.cnblogs.com/812-xiao-wen/p/10544546.html

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值