专栏文章
笔记 · 素性判断等(Miller-Rabin,Pollard-Rho)
个人记录参与者 1已保存评论 0
文章操作
快速查看文章及其快照的属性,并进行相关操作。
- 当前评论
- 0 条
- 当前快照
- 1 份
- 快照标识符
- @mio5ygdu
- 此快照首次捕获于
- 2025/12/02 13:54 3 个月前
- 此快照最后确认于
- 2025/12/02 13:54 3 个月前
Miller-Rabin
两个引理
引理 :费马小定理
设 ,,且 ,那么有
证明:
对于两个整数 ,和一个和 互素的整数 ,则有 .
考虑两组数:;。根据上述结论,两者均为模 的完全剩余系。
因此:
引理 :二次探测定理
如果 ,且 ,则方程 的解为 .
证明:
易知
算法流程
费马小定理的缺陷是,有一些特殊的整数,虽然 ,但 是合数。我们可以考虑通过随机化的方式,来规避这种数,这也是 Miller-Rabin 算法的核心思想。
- 对于 和偶数,可以直接判断。
- 设要测试的数是 。我们取一个较小的质数 。设 (其中 是奇数)。这有什么用呢?后面会说到。
- 对于 ,我们不断去平方他,进行二次探测检验。根据二次探测定理,如果 ,但 且 ,那么 就一定不是质数,退出。
- 我们最多可以平方 次,显然这期间 满足 。最后 ,再使用费马小定理进行验证。如果 ,那么 肯定不是质数,退出。
- 注意,即使通过了上述检验, 也可能不是质数。那么我们就多取几个质数 ,确保了检验的准确性。
注意事项
- 取遍小于等于 的所有质数,就可以保证
int范围内的数不会出错。 - 注意求平方的过程中,可能会爆
long long,需要用到龟速乘。怕超时的话,直接开__int128 - 对于单个数 ,复杂度为 ,其中 为检验次数,此处 。可以保证结果大概率正确。
代码
CPP#include <bits/stdc++.h>
#define int long long
using namespace std;
int prime[10]={2,3,5,7,11,13,17,19,23,29};
int qmul(int a,int b,int mod){
int ans=0;
while(b){
if(b&1) ans=(ans+a)%mod;
a=(a+a)%mod;
b>>=1;
}
return ans;
}
int qpow(int a,int b,int mod){
int ans=1;
while(b){
if(b&1) ans=ans*a%mod;
a=a*a%mod;
b>>=1;
}
return ans;
}
bool Miller_Rabin(int n){
if(n==2) return 1;
if(n<2||!(n&1)) return 0;
//特判0,1,2和偶数
int s=0,t=n-1;
while(!(t&1)){
s++;
t>>=1;
}
for(int i=0;i<10&&prime[i]<n;i++){
int a=prime[i];
int x=qpow(a,t,n);
for(int j=1;j<=s;j++){
int y=qmul(x,x,n);
if(y==1&&x!=1&&x!=n-1) return 0;//二次探测检验
x=y;
}
if(x!=1) return 0;//此时x=a^{n-1},用费马小定理
}
return 1;
}
signed main(){
int n;
cin>>n;
cout<<(Miller_Rabin(n)?"YES\n":"NO\n");
}
Pollard-Rho
算法概述
该算法能快速找到大整数的一个非1、非自身的因子。
首先,可以考虑生成 的随机数 ,计算 ,若 ,那么就找到了, 就是一个 的一个因子。
最坏情况下,, 是一个素数。那么当 时有 ,一共有 个这样的 ,所以最差期望时间复杂度是 的。(log是算gcd带来的)
生日悖论
对于一个 内整数的理想随机数生成器,生成序列中第一个重复的数字前期望有 个数。(生日悖论)
对于 ,在 间生成的随机数模 ,大致是 间的随机数。因此期望在生成大约 个数后,可以出现两个在模 意义下相同的数。那么这两个数的差 ,就满足 ,也就满足 ,这时就找到了。但问题在于,我们没法两两进行比较验证,否则时间复杂度就会退回,还是没有进步。
伪随机数生成器
接下来是 Pollard-Rho 算法的精髓——使用一种特别的伪随机数生成器来生成 内的伪随机数序列。
设序列第一个数是 ,则 是生成的伪随机数序列。
观察这个伪随机数生成器,刚开始的时候是随机的,到后面就会进入一个环。因为下一个数是依赖于上一个数的,而可生成的数又是有限的,因此会有环。所以这个伪随机数生成器生成的数,可以看作是一条链,最终连向一个环。看起来就像字母 。
我们可以使用龟兔赛跑算法(Floyd判环法)。
- 设置两个变量 。每次判断是否有 ,即判断 。有的话,我们就找到了。这个期望显然是 。
- 如果没有,那么令 , 跑的快,所以如果最后没有找到答案,最终会在环上追上 。这时退出,换一个 重新生成伪随机数。
注意到这个伪随机数生成器有一个性质。
对于 ,有
。
如果 是环上满足条件的两个数,它们之间的距离为 (两者之间间隔的边数),那么它们的后继 的距离也为 ,也满足条件。同理, 也满足条件……
也就是说,如果环上两个相距为 的数满足条件,那么环上所有相距为 的数都满足条件。而在Floyd判环的过程中,由于两个指针的移动速度不同,每次移动都是在检查一个新的距离 ,而这样就无需两两比较了。
这个算法的复杂度依赖于该伪随机数生成器的随机程度。如果它是足够随机的,那么期望时间复杂度就是 。
分块优化
这个 还是有点讨厌。怎么去除呢?我们发现瓶颈在于计算 。
注意到若 ,那么一定有 ,所以我们可以先把一些待选数乘起来,再统一和 求公因数,这样就减少了求 的次数。
所以我们可以每隔 步求一次 。但这样到了后面就会走越来越多步才能退出,如果早就找到答案却迟迟不退出,显然不优,因此可以在倍增取距离的基础上,设定一个固定步数上限 。当然也可以简单地每隔 步就计算一下。这样时间复杂度至此优化至 。当 时,时间复杂度为 。
注意事项:
- 为了方便,通常令 任取。
- 当 时,无论 取多少,都得不到解,需要特判。质数也直接特判返回。
- 一些细节和优化见代码。
例题
模版:P4718
题意: 次询问,每次给出一个正整数 ,如果它是质数输出
Prime,否则输出它最大的质因数。Solution:
基本上就是 Pollar-Rho 和 Miller-Rabin 板子。注意,本题中用龟速乘会超时,只能强转
__int128。还需注意本题中要求找最大质因数。我们可以递归地解决该问题。对于当前数 ,Pollard-Rho 算法每次会找到一个 的一个非1、非自身因数 。我们可以递归地去解决数为 和 的子问题。注意往下递归之前判一下 或者 是否已经是质数了,因为Pollard-Rho算法规避找到自己的方法是“多试几次”,如果本来就是质数的话,就会一直循环循环……
容易证明,递归的底层一定会得到原数的质因数,因为如果当前数是合数就能继续向下递归。我们取最大值即可。
Code:
CPP#include <bits/stdc++.h>
#define int long long
using namespace std;
const __int128 one=1;
int prime[12]={2,3,5,7,11,13,17,19,23,29,31,37};//n拓展到10^18之后,k=10不太够了,这里取k=12
int qpow(int a,int b,int mod){
int ans=1ll;
while(b){
if(b&1) ans=one*ans*a%mod;
a=one*a*a%mod;
b>>=1;
}
return ans;
}
bool Miller_Rabin(int n){
if(n==2) return 1;
if(n<2||!(n&1)) return 0;
int s=0,t=n-1;
while(!(t&1)){
s++;
t>>=1;
}
for(int i=0;i<12&&prime[i]<n;i++){
int a=prime[i];
int x=qpow(a,t,n);
for(int j=1;j<=s;j++){
int y=one*x*x%n;
if(y==1&&x!=1&&x!=n-1) return 0;
x=y;
}
if(x!=1) return 0;
}
return 1;
}
const int C=128;
static std::mt19937 aleph;
int Pollard_Rho(int n){
if(n==4) return 2;
if(Miller_Rabin(n)) return n;
std::uniform_int_distribution<int> rnd(3,n-1);
int t=rnd(aleph),r=t,c=rnd(aleph);
auto f=[=](int x){return (one*x*x%n+c)%n;};
t=f(t),r=f(f(r));
for(int lim=1;t!=r;lim=min(lim<<1,C)){
int fac=1ll;
for(int i=0;i<lim;i++){
fac=one*fac*abs(t-r)%n;
if(!fac) break;//小优化:如果当前乘积是0,后面也没必要乘了,怎么乘还是0,直接退出。
t=f(t),r=f(f(r));
}
int d=gcd(fac,n);
if(d>1) return d;
}
return n;
}
int ans=0;
void mx(int z){
if(z>ans) ans=z;//优化,比max快
}
void dfs(int n){
int d=Pollard_Rho(n);
while(d==n) d=Pollard_Rho(n);//Pollard-Rho会随机找到一个因数,如果刚好找到了n,那就再做几次
int d2=n/d;
if(Miller_Rabin(d)) mx(d);//递归下去前判一下质数,不然死循环
else dfs(d);
if(Miller_Rabin(d2)) mx(d2);
else dfs(d2);
}
int calc(int x){
if(Miller_Rabin(x)) return x;
ans=0;
dfs(x);
return ans;
}
void solve(){
int n;
scanf("%lld",&n);
int x=calc(n);
if(x==n) printf("Prime\n");
else printf("%lld\n",x);
}
signed main(){
srand(time(0));
int T;
cin>>T;
while(T--){
solve();
}
}
相关推荐
评论
共 0 条评论,欢迎与作者交流。
正在加载评论...