洛咕
题意:对于每个数字n(\(n<=10^{18}\))检验是否是质数,是质数就输出Prime;如果不是质数,输出它最大的质因子.
分析:Pollard-Rho算法模板题.
Pollard-Rho算法适用于大整数的分解质因数.这是一个很玄学的算法,理论复杂度为\(N^{1/4}\),实际复杂度靠人品,但总的说来它的实际复杂度还是很优秀的.
在学习Pollard-Rho算法之前,还要学习一个同样玄学的Miller_Rabin算法,该算法适用于大整数的质数判定.因为我们要分解一个整数N,首先要确保它不是质数.
Miller_Rabin算法
首先根据费马小定理:若p是质数,且gcd(a,p)=1,那么 \(a^{p-1}≡1(\mod p)\).我们可以通过这个定理,直接快速幂来判断该数是否为质数.然后费马小定理只是质数的性质之一,不代表满足这个性质就一定是质数(理解充分必要条件).例如\(2^{340}≡1(\mod 341)\),但是341=11∗31不是质数.
于是又有了二次探测定理:若p为质数,且\(x^2\equiv 1\pmod{p}\),那么x=1或x=p−1.
证明:\(x^2\equiv 1\pmod{p}\)可以写作\(x^2-1\equiv 0\pmod{p}\),即\((x+1)*(x-1)\equiv 0\pmod{p}\),即\(p|(x+1)*(x-1)\).因为p是一个质数,所以\(p=x+1\)或\(p=x-1(\mod p)\),所以\(x=p-1\)或\(x=p+1=1(\mod p)\).
对于一个数我们先跑费马小定理,若满足费马,再二次探测,此时准确率已经很高了,因为我们无法证明这个Miller_Rabin算法是百分百准确率(实际上好像也达不到),所以只能说它是一个高效的随机算法.代码实现:
inline bool mr(LL x,LL p){
if(ksm(x,p-1,p)!=1)return 0;//费马
rg LL k=p-1;
while(k%2==0){
k>>=1;//x^(p-1)变成(x^((p-1)/2))^2
LL t=ksm(x,k,p);//二次探测
if(t!=1&&t!=p-1)return 0;
if(t==p-1)return 1;
//t=1时还能够继续探测,
//而t=p-1时无法继续探测,只好认为该数是质数
}
return 1;//通过了所有检验,只好认为该数是质数
}
回到正题Pollard-Rho算法,我们判完了不是质数之后,就开始考虑质因数分解.不想讲了
这个算法每个人的模板都不一样.然后洛咕上这道题精心卡常爆long long,所以用到了__int128还有一些优化.
#include<bits/stdc++.h>
#define rg register
#define LL long long
using namespace std;
inline LL read(){
rg LL s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){s=s*10+ch-'0';ch=getchar();}
return s*w;
}
LL T,n,ans;
inline LL ksm(LL a,LL b,LL c){
rg LL cnt=1;
while(b){
if(b&1)cnt=(__int128)(cnt*a)%c;
a=(__int128)(a*a)%c;
b>>=1;
}
return cnt%c;
}
inline bool mr(LL x,LL p){//Miller_Rabin算法
if(ksm(x,p-1,p)!=1)return 0;
rg LL k=p-1;
while(k%2==0){
k>>=1;LL t=ksm(x,k,p);
if(t!=1&&t!=p-1)return 0;
if(t==p-1)return 1;
}
return 1;
}
inline bool pd_prime(LL n){
if(n==2||n==7||n==19||n==61||n==73)return true;
return mr(2,n)&&mr(7,n)&&mr(19,n)&&mr(61,n)&&mr(73,n);
}//随机选几个质数来判定n是不是质数.
inline LL gcd(LL a,LL b){
if(b==0)return a;
return gcd(b,a%b);
}
inline LL pr(LL n){//Pollard-Rho算法
rg LL x,y,cs,sum,i,j,d;
while(1){
y=x=rand()%n;cs=rand()%n;
//算法中的那个随机数公式y=(x*x+cs)%n
sum=1;//超级大优化,下面讲
i=0;j=1;//i一步一步走,j乘2乘2走,判环
while(++i){
x=((__int128)x*x+cs)%n;
sum=(__int128)abs(y-x)*sum%n;
//我们把所有y-x的乘积存到sum里,最后一起与n求gcd
//这样是不影响正确性的(证明略)
//试想如果每一次都gcd,肯定会超时
if(x==y||sum==0)break;
//x=y说明走到了环
if(i%127==0||i==j){
//i%127==0指每找到127个(y-x)求一次gcd
//127可以随意指定.
d=gcd(sum,n);
if(d>1&&d!=n)return d;
if(i==j)y=x,j<<=1;
//如果i追上了j,则j要乘2往前走了.
//y记录x这一次的位置,上面更新完x之后,如果x=y就是环
}
}
}
}
inline void find(LL n){
if(n<=ans)return;//最优性剪枝
if(pd_prime(n)){ans=n;return;}
rg LL p=pr(n);while(n%p==0)n/=p;
find(p);find(n);
}//因为本题要找最大的质因子,就需要find函数来递归
int main(){
T=read();
while(T--){
ans=1;n=read();find(n);
if(ans==n)printf("Prime\n");
else printf("%lld\n",ans);
}
return 0;
}
如果学会了exgcd,还可以去做[CQOI2016]密钥破解这道题,保证就是Pollard-Rho和exgcd的模板.