$ 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 $ ,你需要快速求出它的一个非平凡因子。

对于一个比较小的数( $ 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\times 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}}\times 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;
}

随机推荐

  1. Bootstrap辅助类

    前面的话 Bootstrap提供了一组工具类,用于辅助项目的开发.本文将详细介绍Bootstrap辅助类 文本色 通过颜色来展示意图,Bootstrap 提供了一组工具类.这些类可以应用于链接,并且在 ...

  2. LOJ6045 雅礼集训 2017 Day8 价(最小割)

    由Hall定理,任意k种减肥药对应的药材数量>=k.考虑如何限制其恰好为k,可以将其看作是使对应的药材数量尽量少. 考虑最小割.建一个二分图,左边的点表示减肥药,右边的点表示药材.减肥药和其使用 ...

  3. python的多线程到底有没有用?

    在群里经常听到这样的争执,有人是虚心请教问题,有人就大放厥词因为这个说python辣鸡.而争论的核心无非就是,python的多线程在同一时刻只会有一条线程跑在CPU里面,其他线程都在睡觉.这是真的吗? ...

  4. 【BZOJ3811】玛里苟斯(线性基)

    [BZOJ3811]玛里苟斯(线性基) 题面 BZOJ 题解 \(K=1\)很容易吧,拆位考虑贡献,所有存在的位出现的概率都是\(0.5\),所以答案就是所有数或起来的结果除二. \(K=2\)的情况 ...

  5. [luogu#2019/03/10模拟赛][LnOI2019]长脖子鹿省选模拟赛赛后总结

    t1-快速多项式变换(FPT) 题解 看到这个\(f(x)=a_0+a_1x+a_2x^2+a_3x^3+ \cdots + a_nx^n\)式子,我们会想到我们学习进制转换中学到的,那么我们就只需要 ...

  6. 【转】cJSON 源码分析

    cJSON源码分析 简介 由于C语言汇总,没有直接的字典,字符串数组等数据结构,所以要借助结构体定义,处理json. JSON是一种轻量级的数据交换格式.JSON采用完全独立与语言的文本格式,易于人阅 ...

  7. Shell基础知识(六)

    shell中有很多内建命令,如何区分内建命令与外部文件,使用type command即可看到命令类型. >> type cd # input << cd is a Shell ...

  8. luogu3305/bzoj3130 费用流 (二分答案+dinic)

    Bob肯定想挑一个流量最大的边,然后把所有的费用都加给它呗 那Alice就让流量最大的边尽量小呗 那就二分一下答案再dinic呗 #include<bits/stdc++.h> #defi ...

  9. 树莓派上使用Slowloris进行DDoS攻击

    安装Slowloris工具 git clone https://github.com/gkbrk/slowloris.git 使用Slowloris进行攻击 进入Slowloris文件夹.cd slo ...

  10. 路径或文件名中含有中文的jar文件双击启动不了 -> Java7的Bug?

    至从安装了java7后,才发现部分可执行的jar文件双击都启动不了了. 比如所有的jar文件放在桌面上双击启动不了. 比如所有的文件名中含有中文的jar文件双击启动不了. 比如一个 abc.jar 放 ...