米勒来宾素数测试与Pollard rho算法

米勒来宾素数测试

此处只考虑如何判断奇素数

根据费马小定理:

\(p\)为(奇)素数,\(\gcd(a,p)=1\)时,\(a^{p-1}\equiv 1\pmod p\)

就可以写一个用快速幂判断一个数是不是奇素数

#include<bits/stdc++.h>
using namespace std;
const int plist[]= {2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97};
//素数表
const int L=25;
int fastpow(int a,int k,int mod) {
	int base=1;
	while(k) {
		if(k&1) base=base*1ll*a%mod;
		a=a*1ll*a%mod;
		k>>=1;
	}
	return base;
}
bool isprime(int p) {
	if(p<=100) {//在表内就直接判断
		for(int i=0; i<L; ++i) {
			if(plist[i]==p)return true;
		}
		return false;
	}
	for(int i=0; i<L; ++i) {//否则看看是否符合费马小定理
		if(fastpow(plist[i],p-1,p)!=1)return false;
	}
	return true;
}
int n;
int main() {
	scanf("%d",&n);
	printf(isprime(n)?"YES":"NO");
	return 0;
}

但这个效率低,而且错误的概率很大。

我们可以发现,当\(p\)为奇素数时,\(p-1\)为偶数,就可以写成\(2^r\times m\)的形式(\(m\)为奇数)。

\(a^m\equiv 1\pmod p\)时,一定通过测试。

否则当\(p\)为奇素数时,必定有一个数\(k(0\leq k<r)\)使\(a^{2^k\times m}\equiv p-1\pmod p\),这样再进行平方(指数乘二)后才能是\(1\)

所以我们可以用\(k\)\(0\)枚举到\(r-1\),若有一个\(k\)使\(a^{2^k\times m}\equiv p-1\pmod p\)则通过测试,否则\(p\)不为素数。

同时计算\(a^{2^k\times m}\)不用快速幂,直接用\((a^{2^{k-1}\times m})^2\)计算即可

const int plist[]= {2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97};
//素数表
const int L=25;
inline ll mul(const ll&a,const ll&b,const ll&mod) {  //可能爆ll的计算a*b%mod
    ll r=((long double)a*b/mod+0.5);  //注:用这个不要用太大的模数
    r=a*b-r*mod;  
    return r<0?r+mod:r;  
}//具体为什么可以这样,搜索“骆可强 快速乘”
inline ll fastpow(ll a,ll k,const ll&mod) {  //快速幂
    ll ans=1;  
    while(k) {  
        if(k&1)  
            ans=mul(ans,a,mod);  
        a=mul(a,a,mod);  
        k>>=1;  
    }  
    return ans;  
}
bool mr(const ll&n,const ll&a) {  //用a来hack掉n,是合数就返回true
    ll t=n-1;  
    const ll p=t;  
    while(!(t&1))t>>=1; //除到t为奇数
    ll buf=fastpow(a,t,n);  
    if(buf==1||buf==p)return 0; //通过测试
    while((t<<=1)<p) { //和上面1到r-1是一样的 
        buf=mul(buf,buf,n);  
        if (buf==p)return 0; //通过测试
    }  
    return 1;  
}  
bool ptest(ll n) {  
    if(n<2ll) 
        return 0;  
    if(!(n&1))return n==2; //偶数的情况
	for(int i=0; i<L; ++i) {//小范围的判断整除
		if(n==plist[i])return true;
		if(!(n%plist[i]))return false;
	}
    return n<=10000ll?1:!mr(n,2)&&!mr(n,61)&&!mr(n,233);//用这三个素数就够了  
}  

时间复杂度\(O(\log n)\)

Pollard rho质因数分解

P4718 【模板】Pollard-Rho算法

小结论1(生日悖论)

随便找\(23\)个人,他们之中有任意两人生日相同的概率已经大于\(50\%\)

证明:

不相同的概率:\(\dfrac{365}{365}\times\dfrac{364}{365}\times\cdots\times\dfrac{365-23+1}{365}\)

相同的概率即是\(1\)减去上式,手工算/程序算可以算出是大于\(50\%\)的。

扩展:

随机选出\([1,n]\)中的整数,期望写到\(\sqrt{n}\)个整数时出现重复。

算法思路

先通过一个方法找到一个因子\(s\),之后递归\(s\)\(\dfrac{n}{s}\),递归到质数时更新答案即可。

现在要考虑怎么找一个因子。

假设合数\(n\)有一个因子\(q(1<q<n)\),则我们一定可以找到两个数\(x,y\)使\(x\equiv y\pmod q\)\(x\not\equiv y\pmod n\)

也就是说,我们可以用\(\gcd(|x-y|,n)\)来求出一个对应的因子\(q\)

问题便转换成求一个\((x,y)\)

考虑一个数列\(f_0=1,f_i=F(f_{i-1})\mod n\)

其中\(F\)某种单变量映射,结果和只和参数相关,例如\(F(x)=x^2+c\)\(c\)可以随机取一个)

这个函数还算随机(即算个伪随机数),所以根据上面的小结论,大约到\(\sqrt{n}\)项就可能有环。

每个\(f_i\)的值即可表示为一个类似于\(\rho(rho)\)的东西:(假设\(q(1<q<n)\)\(n\)的一个因数)

第一张是模\(q\)意义下的,其中编号为\(i\)个节点表示\(f_i\mod q\)的值

第二张是模\(n\)意义下的,其中编号为\(i\)个节点表示\(f_i\mod n\)的值

6nU9hQ.png6narRJ.png

可以发现\(f_{11}\equiv f_{4}\pmod q,f_{11}\not\equiv f_4\pmod n\),也就是出现环了,我们就可以通过\(\gcd(|f_{11}-f_{4}|,n)\)求出\(q\)

所以当且仅当\(f_{11}\equiv f_4\pmod n\)时匹配失败。

这个情况特别小,因为\(q\)的环一般远远比\(n\)的环要小,所以更换另一个\(c\)再匹配的复杂度可以忽略不计。

接下来就是要考虑判断环。

追及法求环

我们可以用两个变量\(x1,x2\),初始时\(x1=1,x2=2\),每次\(x1\)往后跳一个,\(x2\)往后跳两个(即\(v_{x2}=2v_{x1}\)

也就是看\(f_{x1},f_{x2}\)就是看\(f_i\)\(f_{2i}\)

那么如果\(q\)有环,前者一定会被后者追上(即多跑整数个环)。

如果\(f_i=f_{2i}=0\),则匹配失败。

否则如果\(\gcd(n,|f_{2i}-f_{i}|)>1\)则匹配成功。

根据小结论,这个是期望\(\sqrt{q}\)的。

若失败,则重置\(F(x)=x^2+c\)\(c\)即可。

小优化

由于\(q\leq\sqrt{n}\),得\(\sqrt{q}\leq n^{0.25}\),所以我们要计算\(n^{0.25}\)\(\gcd\)

也就是说,时间复杂度为\(O(n^{0.25}\log n)\)

我们发现当\(\gcd(a,n)>1,\gcd(b,n)\geq1\)时,\(\gcd(ab\mod n,n)>1\)

我们可以一次存下\(z\)要测试的个值,同时求出它们的积\(\mod n\),然后求与\(n\)\(\gcd\)

大于\(1\)时,这\(z\)个值一定有一个使与\(n\)\(\gcd\)大于\(1\),找到它即可。

否则重新再找\(z\)个值,不断重复上面的操作。

一般\(z\)\(128\)较合适,这样我们就优化掉了一个\(\log\)

时间复杂度\(O(n^{0.25})\)

#include <bits/stdc++.h>
using namespace std;//以下大段为米勒来宾素数测试
const int plist[]= {2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97};
const int L=25;
typedef long long ll;
inline ll read() {
	bool f=1;
	ll x=0;
	int c=getchar();
	while(c<=47||c>=58)f&=c!=45,c=getchar();
	while(c>=48&&c<=57) {
		x=(x<<3)+(x<<1)+(c&15);
		c=getchar();
	}
	return f?x:-x;
}
inline ll mul(const ll&a,const ll&b,const ll&mod) {
	ll r=((long double)a*b/mod+0.5);
	r=a*b-r*mod;
	return r<0?r+mod:r;
}
inline ll fastpow(ll a,ll k,const ll&mod) {
	ll ans=1;
	while(k) {
		if(k&1)
			ans=mul(ans,a,mod);
		a=mul(a,a,mod);
		k>>=1;
	}
	return ans;
}
bool mr(const ll&n,const ll&a) {
	ll t=n-1;
	const ll p=t;
	while(!(t&1))t>>=1;
	ll buf=fastpow(a,t,n);
	if(buf==1||buf==p)return 0;
	while((t<<=1)<p) {
		buf=mul(buf,buf,n);
		if (buf==p)return 0;
	}
	return 1;
}
bool ptest(ll n) {
	if(n<2ll)
		return 0;
	if(!(n&1))return n==2;
	for(int i=0; i<L; ++i) {
		if(n==plist[i])return true;
		if(!(n%plist[i]))return false;
	}
	return n<=10000ll?1:!mr(n,2)&&!mr(n,61)&&!mr(n,233);
}//上面都是米勒来宾素数测试
inline ll gcd(ll a,ll b) {//gcd
	a=abs(a),b=abs(b);
	if(!a)return b;
	for(; b; swap(a,b))
		a%=b;
	return a;
}
const int lim=128;//z
ll sav[150];//存z个值的数组
int tot;
ll prho(const ll&n,const ll&c) {//追及法求环
	ll x1=c+1,x2=mul(x1,x1,n)+c,buf=1;//先取第一个fi
	tot=0;
	if(x1>=n)x1-=n;
	if(x2>=n)x2-=n;
	while(1) {
		buf=mul(x1-x2,buf,n);//乘上要测试的数的值
		++tot;
		sav[tot]=x1-x2;//存下要测试的数
		if(tot==lim) {//够z个数了
			if(gcd(buf,n)>1)break;//满足要求
			tot=0;//重新开始存
		}
		x1=mul(x1,x1,n)+c;
		x2=mul(x2,x2,n)+c;
		x2=mul(x2,x2,n)+c;//往后跳
	}
	for(int i=1; i<=tot; ++i) {
		buf=gcd(sav[i],n);
		if(buf>1)return buf;//返回值
	}
	return n;//不成功
}
void solve(const ll&n,ll&ans) {
	if(n<=ans)return ;//剪枝
	if(ptest(n)) {//是素数
		ans=max(ans,n);
		return ;
	}
	const ll p=n-1;
	ll sav=prho(n,rand()%p+1);
	while(sav==n)sav=prho(n,rand()%p+1);//没成功就换一个c值
	solve(sav,ans);//递归
	solve(n/sav,ans);
}
ll pollard_rho(ll n) {
	ll ans=0;
	if(!(n&1)) {
		ans=2;
		while(!(n&1))n>>=1;
	}
	const int p=sqrt(min(10000ll,n));
	for(int i=3; i<=p; i+=2)
		if(!(n%i)) {
			ans=i;
			n/=i;
			while(!(n%i))n/=i;
		}//上面是先把小范围的素数判了
	solve(n,ans);
	return ans;
}
int main() {
	srand(233);
	for(int T=read(); T; --T) {
		ll n=read();
		ll ans=pollard_rho(n);
		if(ans==n)printf("Prime\n");
		else printf("%lld\n",ans);
	}
	return 0;
}
posted @ 2021-03-10 18:00  mod998244353  阅读(159)  评论(0编辑  收藏  举报
Live2D