质数
若一个正整数无法被除了 1 1 1 和它自身之外的任何自然数整除,则称该数为质数(素数),否则称该正整数为合数,素数总是成对出现的。
在整个自然数集合中,质数的数量不多,分布比较稀疏,对于一个足够大的整数 N N N,不超过 N N N 的质数大约有 N / I n N N/InN N/InN 个,即每 l n N lnN lnN 个数中大约有 1 1 1 个质数。
0 0 0 和 1 1 1 不是素数也不是合数。
质数的判定
(1)试除法
根据定义我们只需要扫描
2
2
2~
N
\sqrt N
N 之间的所有整数,依次检查它们能否整除
N
N
N,若都不能整除,则
N
N
N 是质数,否则为合数。这种方法称为试除法时间复杂度为
O
(
N
)
O(\sqrt N)
O(N)。当然我们需要特判
0
0
0 和
1
1
1 这两个整数,它们即不是质数也不是合数。
//我们可以使用这个函数判断一个数是否为质数
bool isprime(int n){
if(n<2)return false;
for(int i=2;i<=sqrt(n);i++){
if(n%i==0)return false;
}
return true;
}
加速技巧:大于 4 4 4 的素数总是等于 6 x + 1 6x+1 6x+1 或 6 x − 1 6x-1 6x−1。
//加速后的判断函数
bool isPrime(int n){
if(n<=3) return n>1;
//不在6的倍数的两侧的数一定不是质数
if(n%6!=1 && n%6!=5) return false;
for(int i=5; i<=sqrt(n); i+=6){
if(n%i==0 || n%(i+2)==0) return false;
}
return true;
}
质数的筛选
给定一个整数
N
N
N,求出
1
1
1~
N
N
N 之间所有的质数,称为质数的筛选问题。
(1)
E
r
a
t
o
s
t
h
e
n
e
s
Eratosthenes
Eratosthenes(厄拉托塞师)筛法
E
r
a
t
o
s
t
h
e
n
e
s
Eratosthenes
Eratosthenes 筛法基于:任意整数的
x
x
x 的倍数
2
x
,
3
x
,
⋯
2x,3x,\cdots
2x,3x,⋯ 都不是质数。
所以,我们可以从
2
2
2 开始,由从小到大扫描每个数
x
x
x,把它的倍数
2
x
,
3
x
,
⋯
,
[
N
/
x
]
∗
x
2x,3x,\cdots ,[N/x]*x
2x,3x,⋯,[N/x]∗x 标记为合数。当扫描到一个数时,若它尚未被标记,则它不能被
2
2
2~
x
−
1
x-1
x−1 之间的任何数整除,该数就是质数。
E
r
a
t
o
s
t
h
e
n
e
s
Eratosthenes
Eratosthenes 筛法的进行过程如下:
我们可以发现,
2
2
2 和
3
3
3 都会把
6
6
6 标记为合数。实际上,小于
x
2
x^2
x2 的
x
x
x 的倍数在扫描更小的数时就已经被标记过了。因此,我们可以对
E
r
a
t
o
s
t
h
e
n
e
s
Eratosthenes
Eratosthenes 筛法进行优化,对于每个数
x
x
x,我们只需要从
x
2
x^2
x2 开始,把
x
,
(
x
+
1
)
∗
x
,
⋯
,
[
N
/
x
]
∗
x
x,(x+1)*x,\cdots ,[N/x]*x
x,(x+1)∗x,⋯,[N/x]∗x 标记为合数即可。
void primes(int n){
memset(v,0,sizeof(v));//合数标记
for(int i=2;i<=n;i++){
if(v[i]) continue;
cout <<i<<endl;//i是质数
for(int j=i;j<=n/i;j++) v[i*j]=1;
}
}
E r a t o s t h e n e s Eratosthenes Eratosthenes 筛法的时间复杂度为 O ( ∑ 质 数 p ≤ N N p ) = O ( N l o g l o g N ) O(\sum_{质数p\leq N}\frac{N}{p})=O(N\ log\,log\ N) O(∑质数p≤NpN)=O(N loglog N)。该算法实现简单,效率非常接近线性,是算法竞赛中最常用的质数筛法。
(2)线性筛法
质因数分解
任何一个大于
1
1
1 的正整数都能唯一分解为有限个质数的乘积,可写作:
N
=
p
1
c
1
p
2
c
2
⋯
p
m
c
m
N=p_1^{c_1}p_2^{c_2}\cdots p_m^{c_m}
N=p1c1p2c2⋯pmcm
其中
c
i
c_i
ci 都是正整数,
p
i
p_i
pi 都是质数,且满足
p
1
<
p
2
<
⋯
p
m
p_1<p_2<\cdots p_m
p1<p2<⋯pm。
结合质数判定的 “试除法” 合质数筛选的 “
E
r
a
t
o
s
t
h
e
n
e
s
Eratosthenes
Eratosthenes” 筛法,我们可以扫描
2
2
2~
N
\sqrt N
N 的每个数
d
d
d,若
d
d
d 能整除
N
N
N,则从
N
N
N 中除掉所有的因子
d
d
d,同时累计除去的
d
d
d 的个数。
因为一个合数的因子一定在扫描到这个合数之前就从
N
N
N 中被除掉了,所以在上述过程中能整除
N
N
N 的一定是质数。最终就得到了质因数分解的结果,易知时间复杂度为
O
(
N
)
O(\sqrt N)
O(N)。
特别的,若
N
N
N 没有被任何
2
2
2~
N
\sqrt N
N 的数整除,则
N
N
N 是质数,无需分解。
void divide(int n){
m=0;
for(int i=2;i<=sqrt(n);i++){
if(n%i==0){//i是质数
p[++m]=i,c[m]=0;
while(n%i==0) n/=i,c[m]++;
}
}
if(n>1)//n是质数
p[++m]=n,c[m]=1;
for(int i=1;i<=m;i++){
cout<<p[i]<<"^"<<c[i]<<endl;
}
}
附录
- 质数的其他判定法(大素数)
在质数的判定中,试除法作为最简单也最经典的确定性算法,是我们在算法竞赛中常用的方法。有一些效率更高的随机性算法,例如“Miller=Robbin”等,有较小的概率把合数错误判断为质数,但多次判定合起来的错误概率趋近于零。也可以使用费马小定理 x p = x ( m o d p ) x^p=x(mod\ p) xp=x(mod p)。