超浅谈素数
前言
一些关于素数的东西,有误请指正。
Update
2021-114-514:开坑。
2024-9-26:增加前言,改名关于素数,增加欧拉筛。
2025-2-6:乱改。
2025-2-7:又学了一坨。
2025-2-10:改名超浅谈素数。
定义
素数(prime number),又称质数,其定义为只能被
性质
- 存在无穷多个素数。
- 素数分布随整数增大越来越稀疏,随机整数
x 是素数的概率为\frac{1}{\log_2 x} (证明:波特兰定理)。 - 素数定理:定义
\pi(x) 小于等于x 的素数个数,随x 的增长,\pi(x) 与\frac{x}{\ln x} 的比值趋于1 。 - 威尔逊定理:若
p 为素数,有(p-1) ! \equiv -1\pmod p ,非素数则不成立。素性检验
试除法
朴素算法,有一些小技巧,只需判断到
\sqrt{n} 即可,时间复杂度为O(\sqrt{n}) 。bool isprime(int n) { if(n==1) return 0; for(int i=2;i*i<=n;i++) if(n%i==0) return 0; return 1; }费马素性检验
- 费马小定理:设
p \in P ,对于所有正整数a ,且\gcd(a,p)=1 ,都有a^{p-1} \equiv 1\pmod p
其逆定理几乎成立,而费马素性检验就是基于费马小定理的逆定理的随机概率法素性测试。
过程:
对于正整数
- 若不满足费马小定理,则一定不是素数。
- 若满足,则很有可能是素数,继续尝试其他
a 值,尝试次数越多则可能性越大,称n 为基于a 的可能素数。
缺陷:存在一类合数,对于所有满足条件的
设尝试次数为
bool Fermat(int n)
{
if(n<3) return n==2;
for(int i=1;i<=8;i++){
int a=rand()%(n-2)+2;
if(qpow(a,n-1,n)!=1) return 0;
}
return 1;
}
Miller-Rabin 素性检验
显然 Carmichael 数的存在使得费马测试的正确性大打折扣,而 Miller-Rabin 素性检验则可以排除 Carmichael 数。
- 二次探测定理:对于奇素数
p ,方程x^2 \equiv1 \pmod p(x \in [1,p]) 仅有两个解x_1=1 和x_2=p-1 。
证明:
移项可得
Miller-Rabin 算法就应用了这个定理。
步骤:
对于正整数
设
此算法时间复杂度为
bool Miller_Rabin(int n)
{
if(n==1)
return 0;
int k=0;
int p=n-1;
while(!(p&1))
{
p>>=1;
k++;
}
int t=8;
for(int i=0;i<t;i++)
{
int a=rand()%(n-1)+1;
int b=qpow(a,p,n);
bool flag=0;
if(b==1)
continue;
for(int j=0;j<k;j++)
{
if((b+1)%n==0)
{
flag=1;
break;
}
else
b=(b*b)%n;
}
if(flag)
continue;
return 0;
}
return 1;
}
筛法
对于一段连续区间的素数查找,考虑使用筛法来求。
埃氏筛
Eratosthenes 筛,主要的思想就是
时间复杂度为
证明:
观察过程,易得筛的次数为:
由素数定理,
所以,
void getprime(int n)
{
for(int i=2;i<=n;i++)//1不是素数,所以从2开始
{
if(isprime[i]==false)
{
prime[++cnt]=i;//先标记为素数
for(int j=i;i*j<=n;j++)
isprime[i*j]=true;//倍数标记为合数
}
}
}
欧拉筛
对埃氏筛进行改进,注意到在埃氏筛中,一个合数被筛了多次,欧拉筛的改进就是使每个合数只被筛一次,那么用哪个因子筛呢?显然是用最小素因子。
时间复杂度为线性的
void Prime()
{
for(int i=2;i<=n;i++)
{
if(!a[i])
prime.push_back(i);
for(int j=0;j<prime.size();j++)
{
if(i*prime[j]>=n)//判越界
break;
a[i*prime[j]]=1;//用最小素因子去筛
if(i%prime[j]==0)//若不是最小素因子,说明已经被筛过了
break;
}
}
}
线性筛求 \pi(x)
void getprime(int n)
{
memset(isprime,1,sizeof isprime);
isprime[1]=0;
pi[1]=0;
for(int i=2;i<=n;i++)
{
if(isprime[i])
{
prime[++cnt]=i;
pi[i]=cnt;
}
else
pi[i]=pi[i-1];
for(int j=1;j<=cnt&&i*prime[j]<=n;j++)
{
isprime[i*prime[j]]=0;
if(i%prime[j]==0)
break;
}
}
}
质因数分解
- 整数唯一分解定理(算术基本定理):
\forall n \in N_+ ,都有n=\prod_{p_i \in P,c_i \in N_+} p_i^{c_i} 。
线性筛求最小素因子
只需将筛数组用于记录最小素因子即可。
void getprime(int n)
{
memset(vis,0,sizeof vis);
int cnt=0;
for(int i=2;i<=n;i++)
{
if(!vis[i])
{
prime[++cnt]=i;
vis[i]=i;
}
for(int j=1;j<=cnt&&i*prime[j]<=n;j++)
{
vis[i*prime[j]]=prime[j];
if(i%prime[j]==0)
break;
}
}
}
试除法
同样,考虑暴力。
对于
void factor(int n){
int p[25],c[25];
int cnt=0;
for(int i=2;i<=sqrt(n);i++){
if(n%i==0){
p[++cnt]=i,c[cnt]=0;
while(n%i==0) n/=i,c[cnt]++;
}
}
if(n>1) p[++cnt]=n,c[cnt]=1;
}
Pollard-Rho
Pollard-Rho 是一种启发式随机化算法。
考虑找出
考虑如何找
对于
按此规律迭代下去,在后面会形成一个循环节,很好证明,由于在模
生日悖论:
假设每年都是
设有
因为
于是
解得
生日悖论带给我们一个启发,随机选取一列数,出现重复数字的抽样规模期望是
再考虑构造另一个数列
由于
所以它的循环节期望长度为
考虑在数列
考虑判断循环节以确定寻找因子的次数上限,判环有两种方式,Floyd 和 Brent,Floyd 判环的思想是让
不使用优化的期望时间复杂度为
基于 Brent 判环的 Pollard_Rho:
inline int pollard_rho(int n){
int i=1,k=2;
int c=rand()%(n-1)+1;
int x=rand()%n;
int y=x;
while(1){
i++;
x=(qmul(x,x,n)+c)%n;
int d=gcd(abs(y-x),n);
if(d!=1&&d!=n) return d;
if(y==x) return n;
if(i==k) y=x,k<<=1;
}
}
void fac(int n){
if(n<=max_factor||n==1) return;
if(miller_rabin(n)){
max_factor=max(max_factor,n);
return;
}
int p=n;
while(p>=n) p=pollard_rho(p);
while(n%p==0) n/=p;
fac(p); fac(n);
}
这个复杂度只能算小优化,我们考虑把 log 消掉,虽然可以用 Stein 算法优化 gcd,但是收效甚微,考虑使用倍增优化 pollard_rho 函数,而 Brent 判环为我们提供了倍增优化的条件,我们考虑把每次
证明:
如果
所以考虑只计算
那我们考虑取几个
使用这个优化可以过模板题,实测很快。
Code
#include<bits/stdc++.h>
#define int __int128
#define inf 1e18
#define db double
#define gc getchar
#define pc putchar
#define rg register
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
using namespace std;
inline int read(){
int x=0,f=1;
char ch=gc();
while(!isdigit(ch)){
if(ch=='-') f=-f;
ch=gc();
}
while(isdigit(ch)) x=(x<<3)+(x<<1)+ch-'0',ch=gc();
return x*f;
}
inline void write(int x){
if(x<0) pc('-'),x=-x;
if(x>9) write(x/10);
pc(x%10+'0');
}
#define ctz __builtin_ctzll
int max_factor;
inline int gcd(int a, int b){
if(!a||!b) return a|b;
if(a==1||b==1) return 1;
int az=ctz(a),bz=ctz(b),z=min(az,bz),t; b>>=bz;
while(a) a>>=az,t=a-b,az=ctz(t),b=min(a,b),a=t<0?-t:t;
return b<<z;
}
inline 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;
}
inline bool miller_rabin(int n)
{
if(n==1) return 0;
if(n==2||n==3) return 1;
int k=0,p=n-1;
while(!(p&1)) p>>=1,k++;
for(int i=0;i<8;i++){
int a=rand()%(n-1)+1;
int b=qpow(a,p,n);
if(b==1) continue;
int j;
for(j=0;j<k;j++){
if(b==n-1) break;
else b=b*b%n;
}
if(j==k) return 0;
}
return 1;
}
inline int pollard_rho(int n){
int i=1,k=2;
int c=rand()%(n-1)+1;
int x=rand()%n;
int y=x,s=1;
while(1){
i++;
x=(x*x%n+c)%n,s=s*(y-x<0?x-y:y-x)%n;
if(y==x||!s) return n;
if(i==k){
int d=gcd(s,n);
if(d>1) return d;
y=x,k<<=1;
}
}
}
void fac(int n){
if(n<=max_factor||n==1) return;
if(miller_rabin(n)){
max_factor=max(max_factor,n);
return;
}
int p=n;
while(p>=n) p=pollard_rho(p);
while(n%p==0) n/=p;
fac(p); fac(n);
}
signed main(){
int T=read();
while(T--){
srand(11451919);
max_factor=0;
int n=read();
fac(n);
if(max_factor==n) puts("Prime");
else{
write(max_factor);
puts("");
}
}
return 0;
}