$ 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. python----内置函数2与匿名函数

    1.迭代器生成器相关 range:创建一个可迭代对象,一般与for混合使用,可设置步长. for i in range(0,10,2): #步长2范围为0-10不包括10 print(i) # 0 2 ...

  2. 如何在Anaconda中实现多版本python共存

    anaconda中Python版本是3.5,因为爬虫原因,需要Python2.7版本,因此,希望能在anaconda中Python3和Python2共存. 1. 打开Anaconda Prompt,可 ...

  3. Java过滤器Filter的使用详解

    过滤器 过滤器是处于客户端与服务器资源文件之间的一道过滤网,在访问资源文件之前,通过一系列的过滤器对请求进行修改.判断等,把不符合规则的请求在中途拦截或修改.也可以对响应进行过滤,拦截或修改响应. 如 ...

  4. zxing生成二维码设置边框颜色

    真是研究了很久很久,满满的泪啊 zxing生成二维码,默认是可以增加空白边框的,但是并没有可以设置边框颜色的属性. 其中增加空白边框的属性的一句话是: Map hints = new HashMap( ...

  5. 【Gym 100015B】Ball Painting(DP染色)

    题 There are 2N white balls on a table in two rows, making a nice 2-by-N rectangle. Jon has a big pai ...

  6. BZOJ2150 部落战争 【带上下界最小流】

    题目链接 BZOJ2150 题解 复习: 带上下界网络流两种写法: 不建\(T->S\)的\(INF\)的边,即不考虑源汇点,先求出此时超级源汇的最大流,即无源汇下最大的自我调整,再加入该边,求 ...

  7. 洛谷 P2725 邮票 Stamps 解题报告

    P2725 邮票 Stamps 题目背景 给一组 N 枚邮票的面值集合(如,{1 分,3 分})和一个上限 K -- 表示信封上能够贴 K 张邮票.计算从 1 到 M 的最大连续可贴出的邮资. 题目描 ...

  8. scrapy-redis爬取豆瓣电影短评,使用词云wordcloud展示

    1.数据是使用scrapy-redis爬取的,存放在redis里面,爬取的是最近大热电影<海王> 2.使用了jieba中文分词解析库 3.使用了停用词stopwords,过滤掉一些无意义的 ...

  9. module 'scipy.misc' has no attribute 'toimage',python

    anaconda环境下: 错误:python 命令行运行出错:module 'scipy.misc' has no attribute 'toimage' 解决:打开Anaconda prompt,输 ...

  10. 【推荐】使用Ultrapico Expresso学习正则表达式

    Ultrapico Expresso是我工作中经常使用的一个非常强大的正则表达式构建.测试以及代码生成工具.它能够对你构建的正则表达式进行解析.验证,并输出解析结果,提供性能测试工具,支持C#.VB等 ...