Miller-Rabin and Pollard-Rho
Miller-Rabin
她是一个素数判定的算法。
首先需要知道费马小定理
和二次探测定理
证明
正确的运用平方差公式:
然而对于素数 ,有(零因子)
所以原命题得证。
Miller-Rabin(设要被检验的数为 , 的情况请特判):
- 将 拆成 的形式,并随机选出一个整数 ,构造此 Miller 序列:
-
由于费马小定理,若 ,则 一定不是素数;若 ,则 可能是素数。
-
若 没有被第二步筛掉,即还有机会是素数,则研究 :若
,则二次探测定理成立, 可能是素数;否则二次探测定理不成立, 一定不是素数。 -
若 ,则接下来不能用二次探测定理判定了,直接返回 为素数。
-
若 没有被第三步筛掉,即还有机会是素数,则研究 :若
,则二次探测定理成立, 可能是素数;否则二次探测定理不成立, 一定不是素数。 -
递归地使用第四第五两步,若到了 还没有出结果,即 ,则返回 为素数。
-
重复多次上述过程,即 多次随机。只有当每次结果都返回是素数时,最终判定结果才是素数,否则不是素数。
举个例子,
若要判定 是否为素数:
。 取 ,则构造的 Miller 序列分别为:
发现 和 不满足二次探测定理,断定了 不是素数,事实确实如此。
点击查看代码
inline bool MR(ll n){//Miller-Rabin #define Times 80//检验次数 if(n<2 || !(n&1))return (n==2);//特判 ll a,b,m=n-1,cnt=0;//cnt:2因子的个数 while(!(m&1)){ cnt++; m>>=1; } uid(R,1,n-1);//随机数init,这里用mt19937 For(i,1,Times){ a=Rand(R);//随机值 a=pw(a,m,n);//快速幂 For(j,1,cnt){ b=a*a%n;//此时b为M_j,a为M_{j-1} if(b==1 && a!=1 && a!=n-1)//二次探测定理都不行qwq return false; a=b; } if(b!=1)//费马小定理都不行qwq return false; } return true; }
发现这样常数太大,若将 Times
调小,则有些素数会误判。
优化
选取几个特定的优秀的素数作为 ,此时还要特判这些素数
点击查看代码
inline bool mr(ll n,ll c){//用c来筛n ll k=n-1,cnt=0; while(!(k&1)){ k>>=1; cnt++; } ll x=pw(c,k,n),y=x; if(x==1) return true; while(cnt--){ y=pw(x,2,n); if(y==1 && x!=n-1 && x!=1) return false; x=y; if(x==1) return true; } return false; } vector<ll> pr={2,3,5,7,13331,998244353}; //素数选得好,AC不能少 inline bool MR(ll x){ if(x<2) return false; for(ll i:pr) if(x==i) return true; for(ll i:pr) if(!mr(x,i)) return false; return true; }
Pollard-Rho
她是一个分解大合数的算法。
如果是你,你会如何找到某个大整数的一个非1、非自身的因子?
算法1:试除法
从 到 枚举,判断是否为 的因子。
时间复杂度
算法2:随机数法
从 到 枚举,判断是否为 的因子。
这个算法看上去像是来搞笑的,简直和猴子排序不谋而合,然而它正是Pollard-Rho算法的基础。
最坏情况下的期望时间复杂度
算法3:2的优化
从判断是否为 的因子改成判断是否与 不互素,之后取 即可。
最坏情况为:
此时期望时间复杂度 ( 是 带来的)
为了更好得介绍下面的内容,先引入一堆——
生日悖论(其实不是悖论)
这只是有悖直觉而已 OvO
一个房间里有23个人,则他们中有两人生日相同的概率超过一半(不考虑闰年)。
证明从略。
生日悖论启示我们,如果我们不断在某个范围内生成随机整数,很快便会生成到重复的数,期望大约在根号级别。
对于一个 内整数的随机生成器,生成序列中第一个重复数字的位置的期望为
但这件事意义并没有那么大。正如虽然生日悖论是正确的,但你不一定能在班上遇到和自己生日相同的人,因为这个高概率是在两两比较下才成立的。对这些数两两进行验证,复杂度立刻退化,并没有什么进步。所以我们需要一些技巧。
伪随机数序列
设 (可能受曼德勃罗集的启发吧)
则构造序列
和
由于 的取值只由上一个决定(马尔可夫链)( 看作常数)且值域有限,则此序列一定会形成环。
举个例子:
取 时,序列 的走向为:
生成的序列常常形成这样的 形,这也是为什么 Pollard 把这个算法命名为 (rho) 算法。
算法4:Pollard-Rho
转换一下原问题至:不断求出 的一个非 因数,直到此因数不为 即可。
接下来考虑如何求出 的一个非 因数。
其实就是找 序列中两数 (不一定相邻),使得
我们称整个为 ,环(此图中的 )为 , 的环长为
这伪随机数序列有什么好处呢?其实,这个伪随机数生成器生成的数具有一个性质:
若 上有两个数 位置相差 ( 图中 与 相差 ; 与 相差 或 均可),且
则在 上 位置相差 ,有
证明
上位置相差 的数不可能相同
(递推)
换言之,任意选取 上距离为 的两点 ,判断 是否为 的非 因数。
这些二元组基本上(因为距离为 d 倍数的没算,由于这是概率算法,这些可忽略)代表了 上的所有二元组。
所以我们在一个 上,对于每种距离 ,任意找一组 判断是否有非 因子即可。
若没找到,则 中 换一个随机值构造 重复上述步骤即可。
那怎么快速枚举环上每种距离的两点呢?——
Floyd判环算法(龟兔赛跑算法)
形象理解一下,就是把 看作飞行棋棋盘,有两只小灰鸡从 开始,慢的(称为 )每次走一格,快的(称为 )每次走两格。
即初始
每次
且判断
由于 与 之间的距离先逐渐增大,之后在环上相遇,所以两者之间的距离几乎可以取到所有可能的值。
期望复杂度
点击查看代码
inline ll f(ll x,ll c,ll n){ return (/*__int128*/(lll)x*x+c)%n; } inline ll PR(ll n,ll c){ ll t=f(0,c,n);//turtle ll r=f(f(0,c,n),c,n);//rabbit while(t!=r){ ll d=gcd(abs(t-r),n); if(d>1) return d; t=f(t,c,n); r=f(f(r,c,n),c,n); } return n;//没有找到,重新调整参数c }
据说这个方法分解不了 ,所以最好特判一下。
时间复杂度的证明
设
因为 和 可近似看为随机序列,根据生日悖论可以推出其出现循环的期望步数分别为
由于 ,所以有极大的概率当第一次 时,(即 比 更早进入循环)
此时
为 的倍数,于是求 即可求出 这个因子。
因为 的循环期望步数 ,而 ,再乘上 的时间复杂度 ,则最后是
优化:倍增距离
这个方法复杂度很低了,但是这个 还是看得人不爽,我们考虑如何去掉它。
我们想到
所以我们可以减少求公因数的次数,即先把一些待选数乘起来,再统一与 求公因数。
我们可以每隔 个数(依次增加)求一次公因数,这样只需要求期望 次公因数。
具体做法改为(伪代码):
即 开启了瞬移到 身边的功能,冷却时间倍增。
总期望时间复杂度为
优化:固定步数
倍增距离在常数上有个缺点,就是到后面间隔很大,可能已达成目标却迟迟无法退出。
所以我们也可以龟兔赛跑算法(不带瞬移)每隔固定步数 (取 较优)就求一次公因数。
注意:这里的固定步数不是指固定 之间的距离,而是与Floyd判环算法/龟兔赛跑算法相同,只不过将相邻的 询问堆在一起处理。
总期望时间复杂度为
网上很多模板会把这两种方法结合,即倍增取距离,但又规定一个上限。本人本地测试了一下,似乎跟固定步数的时间差别不大,但 Luogu 上好像只有结合的方法能过,固定步数TLE调不出来 qwq。
点击查看代码
inline ll gcd(ll x,ll y){//定义 gcd(0,x)=x if(x<y) swap(x,y); if(y==0) return x; return gcd(y,x%y); } inline ll f(ll x,ll c,ll n){ return ((lll)x*x+c)%n; } inline ll PR(ll n,ll c){ ll t;//turtle ll r=0;//rabbit ll val;//累乘 ll d;//因数 for(int len=1;;len<<=1){//len:这一阶段的长度 (倍增) t=r; val=1; For(cnt,1,len){//rabbit跑 r=f(r,c,n); val=(lll)val*abs(r-t)%n; if(cnt%127==0){ d=gcd(val,n); if(d>1) return d; } } d=gcd(val,n); if(d>1) return d; } }
求最大因数( Luogu P4718 )
既然可以求一个因数,自然就可以求最大因数,这需要一个递归即可。
点击查看代码
ll ans; void work(ll n){//求n的最大素因数->ans if(n<=ans || n<2) return ; if(MR(n)){//特判素数 ckmx(ans,n); return ; } ll d=n; uid(R,1,n-1); while(d==n) d=PR(n,Rand(R));//找到一个因子(不一定素) while(n%d==0) n/=d; work(n); work(d); }
本文来自博客园,作者:ShaoJia,版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义