定义
若一个正整数无法被除了1和它自身之外的任何自然数整除,则该数为质数,否则该数为合数。
在整个自然数集合中,质数的数量不多,分部比较稀疏,对于一个足够大的整数N,不超过N的质数大约有 \(N / \ In N\)个,即每\(\ In N\)个数中大约有一个质数
一、质数的判定
1、试除法
证明解释略(不会的noip都别想考)
inline bool is_prime(register int n)
{
for(register int i=2;i<=sqrt(n);++i)
if(n%i==0)
return false;
return true;
}
这种算法需要扫描\(2~\sqrt N\)之间的所有整数,依次检查他们能否整除N,复杂度为\(O(\sqrt N)\)
2.Miller-Robbin
试除法速度太慢,我们再学一下Miller-Robbin算法
Miller-Robbin算法度娘的解释
Miller-Rabin算法是目前主流的基于概率的素数测试算法,在构建密码安全体系中占有重要的地位。通过比较各种素数测试算法和对Miller-Rabin算法进行的仔细研究,证明在计算机中构建密码安全体系时, Miller-Rabin算法是完成素数测试的最佳选择。通过对Miller-Rabin 算 法底层运算的优化,可以取得较以往实现更好的性能。[1] 随着信息技术的发展、网络的普及和电子商务的开展, 信息安全逐步显示出了其重要性。信息的泄密、伪造、篡改 等问题会给信息的合法拥有者带来重大的损失。在计算机中构建密码安全体系可以提供4种最基本的保护信息安全的服 务:保密性、数据完整性、鉴别、抗抵赖性,从而可以很大 程度上保护用户的数据安全。在密码安全体系中,公开密钥 算法在密钥交换、密钥管理、身份认证等问题的处理上极其有效,因此在整个体系中占有重要的地位。目前的公开密钥 算法大部分基于大整数分解、有限域上的离散对数问题和椭 圆曲线上的离散对数问题,这些数学难题的构建大部分都需 要生成一种超大的素数,尤其在经典的RSA算法中,生成的素数的质量对系统的安全性有很大的影响。目前大素数的生 成,尤其是随机大素数的生成主要是使用素数测试算法,本 文主要针对目前主流的Miller-Rabin 算法进行全面系统的分析 和研究,并对其实现进行了优化
实际就是判断质数的一种算法
Miller Rabin算法的依据是费马小定理的一个推论
\(a ^ {P-1} \equiv 1(mod P)\)
这个式子什么时候成立呢,就是当P是质数时,这个式子才成立
这样就可以判断P是否是质数
但是后来被人举出了反例
这是否意味着利用费马小定理的思想去判断素数的思想就是错误的呢?
答案是肯定的。
但是如果我们可以人为的把出错率降到非常小呢?
比如,对于一个数,我们有99.99999%的几率做出正确判断,那这种算法不也很优越么?
于是Miller Rabin算法诞生了!
首先介绍一下二次探测定理
如果P是质数且\(a^2 \equiv 1(\mod P)\),那么\(a \equiv \pm 1(\mod P)\)
这个很好证明就不写了qaq
这个定理和素数判定有什么用呢?
首先,根据Miller Rabin算法的过程
假设需要判断的数是p
我们把p−1分解为\(2^k*t\)的形式
当p是素数,有\(a^{2^k*t} \equiv 1(\mod p)\)
然后随机选择一个数a,计算出\(a^t(\mod p)\)
让其不断的自乘,同时结合二次探测定理进行判断
如果我们自乘后的数\((\mod p)=1\),但是之前的数\((\mod p)\neq \pm 1\)
那么这个数就是合数(违背了二次探测定理)
这样乘k次,最后得到的数就是\(a^{p-1}\)
那么如果最后计算出的数不为1,这个数也是合数(费马小定理)
那么正确性如何呢?
老祖宗告诉我们,若p通过一次测试,则p不是素数的概率为2525%
那么经过t轮测试,p不是素数的概率为\(\frac{1}{4^t}\)
我习惯用2,3,5,7,11,13,17,19这几个数进行判断
在信息学范围内出错率为0%(不带高精)
注意在进行素数判断的时候需要用到快速幂。。
这个应该比较简单,就不细讲了
#include <bits/stdc++.h>
#define ll long long
using namespace std;
inline char nc(){
static char buf[100000],*p1=buf,*p2=buf;
return p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++;
}
inline int read()
{
register int x=0,f=1;register char ch=nc();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=nc();}
while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=nc();
return x*f;
}
int N,M;
int Test[10]={2,3,5,7,11,13,17};
inline int Pow(register int a,register int p,register int mod)
{
int base=1;
for(;p;p>>=1,a=(1ll*a*a)%mod)
if(p&1)
base=(1ll*base*a)%mod;
return base%mod;
}
inline bool Query(register int P)
{
if(P==1)
return 0;
int t=P-1,k=0;
while(!(t&1))
++k,t>>=1;
for(register int i=0;i<4;++i)
{
if(P==Test[i])
return 1;
ll a=Pow(Test[i],t,P),nxt=a;
for(register int j=1;j<=k;++j)
{
nxt=(a*a)%P;
if(nxt==1&&a!=1&&a!=P-1)
return 0;
a=nxt;
}
if(a!=1)
return 0;
}
return 1;
}
int main()
{
N=read(),M=read();
while(M--)
puts(Query(read())?"Yes":"No");
return 0;
}
二、质数的筛选
1.Eratosthenes筛法
我们有这样的想法,当一个数p是质数时,2p,3p,4p···就不是质数
另外我们从\(p^2\)开始标记
因为中间的数有比p更小的质因子
我们就珂以写出Erotasthenes筛法
复杂度为\(O(N \log \log N)\)
inline void prime(register int n)
{
memset(v,0,sizeof(v));
for(register int i=2;i<=n;++i)
{
if(v[i])
continue;
write(i),puts("");
for(register int j=i;j<=n/i;++j)
v[i*j]=1;
}
}
2.欧拉筛
但是Erotasthenes筛法还是会背重复标记,比如12会被2和3都标记到
所以我们在标记合数的过程中,每次只要向现有的数(注意:不只是质数)乘上一个质因子,并且它是合数最小质因子
这样每个数只会被筛到1次
复杂度为\(O(N)\)
#include <bits/stdc++.h>
#define N 10000005
using namespace std;
inline char nc(){
static char buf[100000],*p1=buf,*p2=buf;
return p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++;
}
inline int read()
{
register int x=0,f=1;register char ch=nc();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=nc();}
while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=nc();
return x*f;
}
int v[N],prime[N];
inline void primes(register int n)
{
memset(v,0,sizeof(v));
int m=0;
for(register int i=2;i<=n;++i)
{
if(v[i]==0)
{
v[i]=i;
prime[++m]=i;
}
for(register int j=1;j<=m;++j)
{
if(prime[j]>v[i]||prime[j]>n/i)
break;
v[i*prime[j]]=prime[j];
}
}
}
int main()
{
int n=read(),m=read();
primes(n);
while(m--)
{
int x=read();
puts(v[x]==x?"Yes":"No");
}
return 0;
}
三、分解质因数
1.试除法
试除法很简单
inline void divide(register int n)
{
m=0;
for(register int i=2;i<=sqrt(n);++i)
if(n%i==0)
{
p[++m]=i;
c[m]=0;
while(n%i==0)
n/=i,++c[m];
}
if(n>1)
p[++m]=n,c[m]=1;
}
2.Pollard's Rho
朴素算法的复杂度为\(O(\sqrt N)的\),而Pollard's Rho算法的复杂度期望是\(O(n^\frac{1}{4})\)的
这里有一篇翻译过来的论文
此算法依靠着生日悖论,利用随机。设被分解数为p,枚举一个数a并计算d=gcd(a,p)。如果d>1,那么可以将p分为d和p/d两部分,并分别递归直到p是质数为止。那么问题就转为如何枚举了。
考虑一种神秘的枚举方法。先随机并记录一个数,设为x。每次枚举完后,计算\(x_i=x_{i-1}*x_{i-1}+c \mod p\)
则每次枚举的数是\(x_i−x_0\)。容易发现,经过几次枚举后,一定会出现\(x_i=x_j\)的情况。此时就停止枚举,直接返回。
因为某种神秘原因,这种方法效率非常高(玄学)。但是我们不能保留所有的数字来判断是否出现,我们考虑来优化这个过程。先规定一个枚举上限k,然后枚举了k个数后,就将记录的数字更改成当前枚举数字,然后k变大一倍。因为神秘力量的作用,这个过程会非常快速。
总而言之,Pollard-Rho算法的过程是:
1.利用Miller-Robbin算法判断p是否是质数。是质数则直接返回。
2.利用上述过程来尝试分解p。如果分解失败则调整参数c重新枚举。
3.将分解得到的d和p/d分别递归调用,回到1。
#include <bits/stdc++.h>
#define ll long long
using namespace std;
inline char nc(){
static char buf[100000],*p1=buf,*p2=buf;
return p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++;
}
inline ll read()
{
register ll x=0,f=1;register char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return x*f;
}
inline void write(register ll x)
{
if(!x)putchar('0');
static int sta[36];register int tot=0;
while(x)sta[tot++]=x%10,x/=10;
while(tot)putchar(sta[--tot]+48);
}
inline ll Max(register ll x,register ll y)
{
return x>y?x:y;
}
ll Test[10]={2,3,7,61,24251};
inline ll mul(register ll a,register ll b,register ll m)
{
ll d=((long double)a/m*b+1e-8);
ll r=a*b-d*m;
return r<0?r+m:r;
}
inline ll Pow(register ll a,register ll b,register ll m)
{
ll r=1;
for(;b;b>>=1,a=mul(a,a,m))
if(b&1)
r=mul(r,a,m);
return r;
}
inline bool Query(register ll P)
{
if(P==1||P==(ll)46856248255981)
return 0;
if(P==2||P==3||P==5)
return 1;
if(!(P&1)||(P%3==0)||(P%5==0))
return 0;
ll t=P-1;
int k=0;
while(!(t&1))
++k,t>>=1;
for(register int i=0;i<5;++i)
{
if(P==Test[i])
return 1;
ll a=Pow(Test[i],t,P),nxt=a;
for(register int j=1;j<=k;++j)
{
nxt=mul(nxt,nxt,P);
if(nxt==1&&a!=1&&a!=P-1)
return 0;
a=nxt;
}
if(a!=1)
return 0;
}
return 1;
}
inline ll gcd(register ll a,register ll b)
{
if(!a)
return b;
if(!b)
return a;
int t=__builtin_ctzll(a|b);
a>>=__builtin_ctzll(a);
do{
b>>=__builtin_ctzll(b);
if(a>b)
a^=b^=a^=b;
b-=a;
}while(b!=0);
return a<<t;
}
inline ll g(register ll x,register ll n)
{
ll t=mul(x, x, n) + 1;
return t<n?t:t-n;
}
#define M (1<<7)-1
inline ll pollardrho(register ll n)
{
ll x=(rand()%(n-1))+1,y=x,t=1,q=1;
for(register int k=2;;k<<=1,y=x,q=1)
{
for(register int i=1;i<=k;++i)
{
x=g(x,n);
q=mul(q,abs(x-y),n);
if(!(i&M))
{
t=gcd(q,n);
if(t>1)
break;
}
}
if(t>1||(t=gcd(q,n))>1)
break;
}
if (t==n)
{
t=1;
while(t==1)
t=gcd(abs((x=g(x, n))-y),n);
}
return t;
}
int np=0;
ll f[105];
inline void find(register ll x)
{
if(x==1)
return;
if(Query(x))
{
f[++np]=x;
return;
}
ll p=x;
while(p==x)
p=pollardrho(x);
find(p);
find(x/p);
}
int main()
{
srand(19260817);
int m=read();
while(m--)
{
ll x=read();
np=0;
find(x);
sort(f+1,f+1+np);
if(np==1)
puts("Prime");
else
write(f[np]),puts("");
}
return 0;
}