Pollard-Rho 学习笔记
前言
其实很早就看到过了,下定决心去学的,居然是因为翻到之前口胡的题目,然后发现之前做法假了,继续尝试做的时候发现需要这个算法,于是,题目就绿->黑了。
Step.1 引入
求一个数的所有因数,这个问题伴随了我们很久了,现在又要翻出来鞭尸。
最开始的时候,我们使用的是最朴素的
现在,我们需要解决的便是这样一个问题。
Step.2 从悖论出发
悖论,即数学模型与平时的经验产生了极大的反差。
这里将举出几个例子:
生日悖论,如果一共有
如果你没有听过,那么这个悖论可能会让你难以置信,我们不妨来简单计算一下概率。
直接计算
可以得到一个
当
通过计算机可以得到答案为
如果我们再把
再来一个例子,如果我们在
可以感性理解,如果我们选择了一个数
可以发现,我们直接等概率取,想要取到特定的值概率会较小,但是如果我们取多个,并通过限制,也就是扩大范围,就可以有效地让概率提升。
更进一步,我们在一个范围内随机生成数,每多生成一个数,就等于多了一个限制条件,那么想要生成两个相同的数,需要的数期望会很少,可以参考知乎的这个回答,在
Step.3 基于随机数的“乱搞”做法
回到我们需要求的问题,找到
首先,可以直接随机,然后判断是否是因数,但是这样做概率实在太小了,最坏情况甚至会一直找不到,显然还不如
所以考虑扩大范围,来让概率尽可能地提升。
可以发现,我们原本的随机实际上是有损失的,比如:
假设随机到的数是
所以我们完全可以通过
对于合数,与它互质的数字个数不是很多,但是想要保证概率还是困难的。
想到悖论的第二个例子,我们可以选择若干个数
但是显然,如果选择那么多个因子,再两两相减找因子,那复杂度又退化到了 这不是吃饱了没事干吗
不过不需要着急,直到现在,都还没有出现本次的主题 Pollard-Rho 呢。
Step.4 Pollard 随机数立大功
Pollard 构造了一个伪随机数生成的函数:
然后通过递归生成随机数,如,从
因为每次生成都会对
下图就是我随手画的
事实上,说这个序列长得像
就是因为存在环,所以这是一个伪随机生成函数。
那么,为什么不使用现在比较常用的随机数生成方法,而专门构造一个生成方式呢?
这是因为按照这种方式生成的序列具有一个很重要的性质:对于所有的
证明比较简单,先令
那么,对于
那么
同理,
所以
这个性质的作用非常大,但是目前我们还用不到,将会在下一步出现,不过,同样的,如果某个随机数生成方法也有这个性质,那么也可以使用这个随机数生成方法,但是一般情况 Pollard 的随机数生成方法已经足够优秀了。
那么,我们将
根据前面的理论,所有的
这也是为什么复杂度有个奇怪的
Step.5 判环
因为随机数序列存在环,所以判环就是很重要的环节了,如果选择 map 等 STL 较为暴力地判环,复杂度就不够优秀,这里介绍一种 Pollard-Rho 基本上都会用的判环方法--Floyd 判环。需要注意,这个 Floyd 和最短路的 Floyd 不一样,仅仅是同一个人发明的而已。
我们可以想象有两个人在赛跑,如果一个人的速度比另外一个人快,那么他们一定会相遇,并且跑的距离差一定是圈长,但是因为并不是每个时刻都会检查,所以可能要快的人超了慢的人几圈了我们才会发现。
那么现在,这个思想也可以应用于判环,取
另外,
这样,我们就避免了需要两两相减,逐一判断导致复杂度退化回去的尴尬情况,而是判断
但是复杂度依然不是
尽管已经很快了,但是我们需要追求极限,不是吗?
Step.6 积累的力量
假如
选择的数量一般为
那么在有了积累,我们的复杂度就可以成功地降到
Step.7 一些细节
因为 Pollard-Rho 不具备检查是否为质数的能力,如果用一个质数一直去尝试寻找因子,那么就会一直找不到,所以还需要判断素数,可以使用 Miller Rabin 快速判断,并且可能一次 Pollard-Rho 也找不到,所以这种情况可以返回原数,然后重新随机一个
下面给出代码:
inline long long f(long long x,long long c,long long n){return ((lll)x*x+c)%n;}//随机数生成方法,lll转换位__int128防止溢出
inline long long pollard_rho(long long x)
{
long long c=rand()%(x-1)+1,a=0,b=0,mul=1;
do
{
for(long long i=1;i<128;++i)//累计127次
{
a=f(a),b=f(f(b)),mul=(lll)mul*abs(a-b)%x;
if(a==b||!mul) break;//如果相等或者累乘出现0则代表遇到环,应该推出
}
long long gcd=__gcd(mul,x);
if(gcd>1) return gcd;//找到因数了
}while(a!=b)
return x;//每找到
}
inline void get_fac(long long x)
{
if(prime(x)) return;//先判质数
long long p=x;
while(p==x) p=pollard_rho(p);//一直找直到找到为止
}
Step.8 例子
P4718 【模板】Pollard-Rho
判断素数可以直接 Miller Rabin 快速判断,然后发现要求的不是一个因子,而是最大的质因子。
可以考虑递归求解。
每次判断
然后将
inline void sol(long long x)
{
if(x<=ans||x<2) return;//剪枝,如果小于答案则可以返回了
if(prime(x)) return ans=max(ans,x),void();//如果是质数,则更新答案
long long p=x;
while(p==x) p=PR(x);//找因子
while(x%p==0) x/=p;//除去因子
fac(x),fac(p);//递归求解
}