首先贴一个最基本的O( n−−√) n ) 素数判定方法
直接进行因子检测即可
#include<bits/stdc++.h>
using namespace std;
int n;
bool prime(int n)
{
if(n<2)return false;
for(int i=2;i*i<=n;i++)
{
if(n%i==0)return false;
}
return true;
}
int main()
{
while(cin>>n)
{
cout<<prime(n)<<endl;
}
return 0;
}
以上代码虽然达到了O(
n−−√)
n
)
,但是对于大数来说还远远不够。
再来看一个:
Miller-Rabin素数判定算法
- 费马小定理
若p是素数,a是任意数
ap−1≡1modp a p − 1 ≡ 1 mod p - 二次探测定理
若p是奇素数(除2以外)
x2≡1modp x 2 ≡ 1 mod p
它的解为:x=1或者x=p−1(等价于x=−1) x = 1 或 者 x = p − 1 ( 等 价 于 x = − 1 )
明确一点,上述定理都是基于素数的性质得出的结论,而Miller-Rabin算法的思想就是通过检测是否满足素数的性质而大概率确定一个数是否为素数(存在不确定性,但是几率很小) 基于以上定理的Miller-Rabin算法
基于以上两个定理将我们要判定的素数设为n,随机取一个a进行测试 an−1≡1modn a n − 1 ≡ 1 mod n
因为n为奇素数,那么n-1肯定为偶数,将 n=2s∗r n = 2 s ∗ r ,其中r为奇数
则有(((ar)2)2...) ( ( ( a r ) 2 ) 2 . . . )
这样迭代进行s次运算,每次迭代过程必须满足二次探测的解,否则,n直接判定为合数。关于大整数因子分解,采用递归思想,同样引入随机因子c进行随机的但是有循环地构造试探可行因子。
构造过程:随机出 x0和c0 x 0 和 c 0
递归构造:xi=x2i−1+c0 x i = x i − 1 2 + c 0
检测:d=gcd(xi−xi−1,n),且d≠1&&d≠n d = g c d ( x i − x i − 1 , n ) , 且 d ≠ 1 & & d ≠ n
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <time.h>
#include <algorithm>
typedef long long ll;
#define Time 15 //随机算法判定次数,Time越大,判错概率越小
using namespace std;
ll n,ans,factor[10001];//质因数分解结果(刚返回时是无序的)
ll tol;//质因数的个数,数组下标从0开始
//****************************************************************
// Miller_Rabin 算法进行素数测试
//速度快,而且可以判断 <2^63的数
//****************************************************************
const int times = 20;
int number = 0;
long long Random( long long n ) //生成[ 0 , n ]的随机数
{
return ((double)rand( ) / RAND_MAX*n + 0.5);
}
long long q_mul( long long a, long long b, long long mod ) //快速计算 (a*b) % mod
{
long long ans = 0;
while(b)
{
if(b & 1)
{
b--;
ans =(ans+ a)%mod;
}
b /= 2;
a = (a + a) % mod;
}
return ans;
}
long long q_pow( long long a, long long b, long long mod ) //快速计算 (a^b) % mod
{
long long ans = 1;
while(b)
{
if(b & 1)
{
ans = q_mul( ans, a, mod );
}
b /= 2;
a = q_mul( a, a, mod );
}
return ans;
}
bool witness( long long a, long long n )//miller_rabin算法的精华
{//用检验算子a来检验n是不是素数
long long tem = n - 1;
int j = 0;
while(tem % 2 == 0)
{
tem /= 2;
j++;
}
//将n-1拆分为a^r * s
long long x = q_pow( a, tem, n ); //得到a^r mod n
if(x == 1 || x == n - 1) return true; //余数为1则为素数
while(j--) //否则试验条件2看是否有满足的 j
{
x = q_mul( x, x, n );//这里的判断就是从最里层的括号开始判断,一旦条件满足,由于-1和+1的平方肯定为1即满足条件可退出循环
if(x == n - 1) return true;//(等价于x=-1;
}
return false;
}
bool miller_rabin( long long n ) //检验n是否是素数
{
if(n == 2)
return true;
if(n < 2 || n % 2 == 0)
return false; //如果是2则是素数,如果<2或者是>2的偶数则不是素数
for(int i = 1; i <= times; i++) //做times次随机检验
{
long long a = Random( n - 2 ) + 1; //得到随机检验算子 a
if(!witness( a, n )) //用a检验n是否是素数
return false;
}
return true;
}
//************************************************
//pollard_rho 算法进行质因数分解
//************************************************
ll gcd(ll a,ll b)
{
if(a==0)return 1;
if(a<0) return gcd(-a,b);
while(b)
{
long long t=a%b;
a=b;
b=t;
}
return a;
}
ll Pollard_rho(ll x,ll c)
{
ll i=1,k=2;
ll x0=rand()%x;
ll y=x0;
while(1)
{
i++;
x0=(q_mul(x0,x0,x)+c)%x;
long long d=gcd(y-x0,x);
if(d!=1&&d!=x) return d;
if(y==x0) return x;
if(i==k)
{
y=x0;
k+=k;
}
}
}
//对n进行素因子分解
void findfac(ll n)
{
if(miller_rabin(n))//素数
{
factor[tol++]=n;
return;
}
ll p=n;
while(p>=n) p=Pollard_rho(p,rand()%(n-1)+1);
findfac(p);//递归调用
findfac(n/p);
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
scanf("%lld",&n);//(n>=2)
if(miller_rabin(n))
{
printf("Prime\n");
continue;
}
tol=0;
findfac(n);//对n分解质因子
ll ans=factor[0];
for(int i=1; i<tol; i++)
if(factor[i]<ans)
ans=factor[i];
printf("%lld\n",ans);
}
return 0;
}
2.素数打表
- 普通筛选法–埃拉托斯特尼筛法
简单模板:
简单描述一下过程,第一维循环,筛出素数,第二维循环在第一维是素数的情况下筛掉合数
因为有很多的合数有很多的质因子,因此,一个合数可能被多个质数重复筛。
所以时间复杂度是 O(n∗sum) O ( n ∗ s u m ) sum为所有合数的质因子个数和。
#include<bits/stdc++.h>
using namespace std;
const int maxn=100005;
int prime[maxn],cnt;
bool check[maxn];
void seive(int n)
{
memset(check,true,sizeof check);
for(int i=2;i<=n;i++)
{
if(check[i])prime[cnt++]=i;
else continue;
for(int j=i+i;j<=n;j+=i){
check[j]=false;
}
}
}
int main()
{
int n;
while(scanf("%d",&n)!=0)
{
cnt=0;
seive(n);
for(int i=0;i<cnt;i++)
{
printf("%d ",prime[i]);
}
printf("\n");
}
return 0;
}
- 线性筛-欧拉筛
通过对最关键的两行代码分析,每个合数只有一个质数,而每个合数只可能被最小质数判定一次,那么可知算法是严格O(n)的,即所谓的线性筛。
与埃式筛的不同之处在于,欧拉筛的第二维是用质数进行判定合数。
#include<bits/stdc++.h>
using namespace std;
const int maxn=1000005;
int n;
int prime[maxn],cnt;
bool check[maxn];
void seive(int n)
{
memset(check,true,sizeof check);
for(int i=2;i<=n;i++)
{
if(check[i])prime[cnt++]=i;
for(int j=0;j<cnt&&i*prime[j]<=n;j++)
{
check[i*prime[j]]=false;//i不能被前面任何质数整除,
//则其最小质数肯定大于等于当前prime[j],
//即当前合数i*prime[j]的最小质数为prime[j];
if(i%prime[j]==0)break;//从小到大用一个质数对i进行检测,
//一旦整除,后面的肯定不能是更小的质数了,直接跳出。
}
}
}
int main()
{
while(scanf("%d",&n)!=EOF)
{
seive(n);
for(int i=0;i<cnt;i++)
{
printf("%d ",prime[i]);
}
printf("\n");
}
return 0;
}
欧拉筛稍加改造后求出莫比乌斯函数的系数
void seive(int n)
{
memset(check,true,sizeof check);
for(int i=2;i<=n;i++)
{
if(check[i])prime[cnt++]=i,mu[i]=-1;;
for(int j=0;j<cnt&&i*prime[j]<=n;j++)
{
check[i*prime[j]]=false;//i不能被前面任何质数整除,
//则其最小质数肯定大于等于当前prime[j],
//即当前合数i*prime[j]的最小质数为prime[j];
if(i%prime[j]==0)//从小到大用一个质数对i进行检测,
//一旦整除,后面的肯定不能是更小的质数了,直接跳出。
{
mu[i*prime[j]]=0;
break;
}
else
{
mu[i*prime[j]]=-mu[i];
}
}
}
}