一、定义
素数(又称质数)是指在大于1的自然数中,除了1和它本身以外不再有其他因数的自然数。
1 既非素数也非合数,2 是唯一偶素数
二、定理
设 π ( x ) 为1到x中素数的个数
由黎曼推导出下面的精确公式,详情见Riemann Prime Counting Function
然后又有一个神奇的比式极限
让我们得到如下结论:
-
在自然数集中,小于n的质数有如下公式结果个
n l n ( n ) \frac{n}{ln(n)} \quad ln(n)n
(109 大约有48,254,942个素数(数量级为107)) -
伯特兰-切比雪夫定理:若整数n > 3,则至少存在一个质数p,符合n < p < 2n − 2。另一个稍弱说法是:对于所有大于1的整数n,至少存在一个质数p,符合n < p < 2n。
这个性质的应用:【XR-3】小道消息
三、素数判定
试除法
一个数如果不是素数,则一定能被一个小于它自己的数整除。
bool isprime(int x)
{
if(x<2) return false;
for(int i=2; i*i<=x; i++)
{
if(x % i == 0)
return false;
}
return true;
}
预处理法
具体为后面的筛法。
kn+i 法
Miller-Rabin判定法
前言
MillerRabin素数测试是一种很实用的素数判定方法。
预处理法确实节省了不少时间,但是如果素数很大,那么为了判断一个素数就还得从头开始扫描,空间代价也不少,故不能接受。
而Miller-Rabin判定法只针对单个数字进行判定,因而可以对较大的乃至于long long范围内的数进行判定,而且速度也很快,是个十分优秀的算法。
前置知识
- 费马小定理:ap−1 ≡ 1(mod p) (p为素数)
- 二次探测定理:若p为奇素数且 x2 ≡ 1(mod p),则x ≡ 1(mod p)或x ≡ p−1(mod p)
算法流程
1)先判断0,1,2和所有偶数
2)设我们要测试的数为x,取一个质数a,设s,t满足2s · t = x-1 ( t为奇数)
3)算出at ,然后不断自乘并且进行二次探测检验(进行s次),不满足就为合数
4)进行s次后就得到ax-1 ,再判断 ax-1 ≡ 1 (mod x)是否成立,否就为合数
5)取多个不同的a,提高正确性
注:
这不是一个能保证正确的算法,但幸运的是我们可以通过多次执行算法降低错误的概率。
如果经过 t 轮测试,则p不是素数的概率为
1
4
t
\frac {1}{4^t}
4t1
所以当 a 取遍小等于 30 的所有素数时,可以证明 int 范围内的数不会出错。
另外,如果是求一个 long long 类型的平方,可能会爆掉,因此有时我们要用“快速积”,不能直接乘。
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int prime[10]={2,3,5,7,11,13,17,19,23,29};
int Quick_Multiply(int a,int b,int c) //快速积(和快速幂差不多)
{
long long ans=0,res=a;
while(b)
{
if(b&1)
ans=(ans+res)%c;
res=(res+res)%c;
b>>=1;
}
return (int)ans;
}
int Quick_Power(int a,int b,int c) //快速幂,这里就不赘述了
{
int ans=1,res=a;
while(b)
{
if(b&1)
ans=Quick_Multiply(ans,res,c);
res=Quick_Multiply(res,res,c);
b>>=1;
}
return ans;
}
bool Miller_Rabin(int x) //判断素数
{
int i,j,k;
int s=0,t=x-1;
if(x==2) return true; //2是素数
if(x<2||!(x&1)) return false; //如果x是偶数或者是0,1,那它不是素数
while(!(t&1)) //将x分解成(2^s)*t的样子
{
s++;
t>>=1;
}
for(i=0;i<10&&prime[i]<x;++i) //随便选一个素数进行测试
{
int a=prime[i];
int b=Quick_Power(a,t,x); //先算出a^t
for(j=1;j<=s;++j) //然后进行s次平方
{
k=Quick_Multiply(b,b,x); //求b的平方
if(k==1&&b!=1&&b!=x-1) //用二次探测判断
return false;
b=k;
}
if(b!=1) return false; //用费马小定律判断
}
return true; //如果进行多次测试都是对的,那么x就很有可能是素数
}
int main()
{
int x;
scanf("%d",&x);
if(Miller_Rabin(x)) printf("Yes");
else printf("No");
return 0;
}
四、素数的筛法
- 埃氏筛(Eratosthenes 筛法)
思想:如果x是合数,那么他的倍数也一定是合数,那么标记完所有的合数,剩下的就是素数。
更严谨的证明
bool vis[N];
void eshai(int n)
{
vis[1]=1;
memset(vis,0,sizeof(vis));
for(int i=2; i<=n; i++)
{
if(vis[i]) continue;
for(int j=i; j<=n/i; j++)
{
vis[i*j]=1;
}
}
}
- 线性筛(欧拉筛)
欧拉筛的算法能够保证每个数只会被他的最小质因子筛一次,所以时间复杂度是O(n)。
对于每一个合数x,他的最小质因子为y,在x/y时就能被筛出,因此每个合数一定可以被标记到。
欧拉筛还能够得到了每个数的最小质因子。
int n,prime[N],cnt=0;
bool vis[N];
void getprime()
{
for(int i=2; i<=n; i++)
{
if(!vis[i]) prime[++cnt] = i;
for(int j=1; j<=cnt && i*prime[j] <= n; j++)
{
vis[i*prime[j]] = 1;
if(i % prime[j] == 0) break;
}
}
}
参考资料
《算法竞赛中的初等数论》(一)正文 0x00整除、0x10 整除相关(ACM / OI / MO)(十五万字符数论书)
夜深人静写算法(三)- 初等数论入门
初学MillerRabin素数测试
Miller-Rabin素数测试算法