质数
质数
本文部分内容参考李煜东《算法竞赛进阶指南》
更新记录:
- 2026/7/8:质数部分基本完成
概述
::::info[质数的定义] 若一个正整数无法被除 1 和它自身之外的任何自然数整除,则称该数为质数(或素数),否则称该数为合数。特别地,1 既不是质数也不是合数。 ::::
::::info[质数的分布]
在整个自然数集合中,质数的数量不多,分布较为稀疏。对于一个足够大的正整数
一切判定和筛选质数的方法都是从定义出发的,请大家务必深入理解其本质。
一、质数的判定
1. 试除法
若一个正整数
::::success[证明]
由质数定义可知,若
用反证法。假设命题不成立,即所有能整除
证毕。 ::::
根据上述命题,我们只需扫描
::::info[代码实现]
bool is_prime(int n) {
if (n < 2) return false;
for (int i = 2; i <= sqrt(n); i++)
if (n % i == 0) return false;
return true;
}
::::
此外,还有一种效率更高的随机算法——Miller–Rabin 素性测试,但涉及较为深入的数学知识,我们将在后续章节中介绍。
二、质数的筛选
质数的筛选,顾名思义,就是像用筛子一样将质数从众多自然数中筛选出来,而非逐个判断每个数是否为质数。
1. Eratosthenes 筛法(埃氏筛)
任意整数
我们可以从 2 开始,由小到大扫描每个数
观察可以发现,2 和 3 都会将 6 标记为合数,存在重复标记。实际上,小于
::::info[代码实现]
void primes(int n) {
memset(v, 0, sizeof(v)); // 合数标记,0 表示未标记
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;
}
}
::::
埃氏筛的时间复杂度为
2. 线性筛法(欧拉筛)
经过优化后的埃氏筛仍然会重复标记某些合数。线性筛法通过 “从大到小累积质因子” 的方式,保证每个合数只被标记一次。
设数组
- 依次考虑
2 \sim N 之间的每一个数i 。 - 若
v[i] = 0 ,说明i 是质数,将其保存下来,并令v[i] = i 。 - 扫描所有不大于
v[i] 的质数p ,令v[i \times p] = p 。即在i 的基础上累积一个质因子p 。因为p \le v[i] ,所以p 就是合数i \times p 的最小质因子。
每个合数
| 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | |
|---|---|---|---|---|---|---|---|---|---|
| 2 | 2, 3 | 2 | 2, 3, 5 | 2 | 2, 3, 5, 7 | 2 | 2, 3 | 2 | |
| 4 | 6, 9 | 8 | 10, 15, 25 | 12 | 14, 21, 35, 49 | 16 | 18, 27 | 20 |
::::info[代码实现]
int v[MAX_N], prime[MAX_N];
void primes(int n) {
memset(v, 0, sizeof(v)); // 最小质因子,0 表示未确定
int m = 0; // 质数数量
for (int i = 2; i <= n; i++) {
if (v[i] == 0) {
v[i] = i;
prime[++m] = i; // i 是质数
}
// 给当前的数 i 乘上一个质因子
for (int j = 1; j <= m; j++) {
// i 含有比 prime[j] 更小的质因子,或超出 n 的范围,停止循环
if (prime[j] > v[i] || prime[j] > n / i) break;
// prime[j] 是合数 i * prime[j] 的最小质因子
v[i * prime[j]] = prime[j];
}
}
for (int i = 1; i <= m; i++) cout << prime[i] << endl;
}
::::
3. 习题
【模板】线性筛素数
三、质因数的分解
1. 算术基本定理
任何一个大于 1 的正整数都能唯一分解为有限个质数的乘积,可写作:
其中
2. 试除法
结合质数判定中的试除法和质数筛选的思想,扫描
因为一个合数的因子一定在扫描到该合数之前就被从
特别地,若
::::info[代码实现]
void divide(int n) {
int m = 0;
int p[MAX_N], c[MAX_N];
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;
}
::::
Pollard–Rho 算法是一种比试除法效率更高的质因数分解算法,感兴趣的读者可以参考此文。
3. 习题
阶乘分解