素数
可能出现“质数”、“素数”混用的情况,见谅。
定义
一个正整数无法被除了 \(1\) 和它自身之外的任何自然数整除,则称该数为质数,否则称其为合数。
注意到在整个自然数集合中,质数数量不多、分布稀疏,对于一个 足够大 的 \(N \in \mathbb{Z}\),\(\leq N\) 的质数大约有 \(\frac{N}{\ln N}\) 个。换句话说,每 \(\ln N\) 个数中大约有 \(1\) 个质数。
质数判定与筛选
试除法
我们知道若 \(N \in \mathbb{Z}\) 且 \(N\) 为合数,则必定存在一个能整除 \(N\) 的数 \(M\) 满足 \(2 \leq M \leq \sqrt{N}\)。
可以写出代码:
bool IsPrime(int x) {
if (x == 0 || x == 1)
return 0;
for (int i = 2; i * i <= x; i++) {
if (x % i == 0)
return 0;
}
return 1;
}
基于随机化的判定有 Miller-Robbin。
Eratosthenes
思想是对于任意 \(x\) 的倍数 \(x, 2x, 3x, \dots\) 都不是质数,所以从 \(2\) 开始倍数筛掉,往后若一个数 \(x\) 尚未被标记,意味着其不能被 \(\forall [2, x - 1]\) 整除,则其为质数。
然后同上试除法,为了防止形如 \(6 = 3 \times 2\) 同时被 \(2, 3\) 筛两次的情况(小于 \(x^2\) 的数 \(x\) 的倍数在扫描更小的数就已经筛过了),从 \(x^2\) 开始,标记 \(x^2, (x + 1)x, \dots, \lfloor \frac{N}{x} \rfloor x\) 即可。
void Prime(const int n) {
memset(v, 0, sizeof(v));
for (int i = 2; i <= n; i++) {
if (v[i])
continue;
p[++tot] = i;
for (int j = i; j <= n / i; j++)
v[i * j] = 1;
}
}
复杂度为 \(O(\sum_{p \leq N, p \in \mathbb{P}} \frac{N}{p}) = O(N \log \log N)\),接近线性,是 OI 中最常见的筛法。
线性筛
我们是怎么优化暴力筛的?通过枚举倍数来减少总的枚举次数。但是优化后我们的埃氏筛还是会重复标记某些合数,例如 \(12\) 会被 \(2 \times 6\) 和 \(3 \times 4\) 同时标记。那我们能否唯一确定出一种筛选出某个数的做法:即我们 能否确定某个数唯一产生的方式。
我们正式引入线性筛:线性筛通过“从小到大累积质因子”的方式标记每个合数,这里直接给出易懂的代码:
处理的手段其实就是通过确定唯一产生数的方式来确定唯一的“遍历”方式。
int v[N], p[N];
void Prime(const int n) {
m = 0;
memset(v, 0, sizeof(v));
for (int i = 2; i <= n; i++) {
if (!v[i]) { v[i] = i; p[++m] = i; } // 质数
// 给当前的数 i 乘上一个质因子
for (int j = 1; j <= m; j++) {
// i 有比 p[j] 更小的质因子,或者已经超出了 n 的范围
if (p[j] > v[i] || p[j] > n / i)
break;
// p[j] 是合数 i * p[j] 的最小质因子
v[i * p[j]] = p[j];
}
}
}
复杂度为 \(O(n)\),真正的线性筛法。
算数基本定理
这非常重要。
任何一个大于 \(1\) 的正整数都能唯一分解有限个质数的乘积,形式化地:
\[N = p_1^{c_1} p_2^{c_2} \cdots p_m^{c_m} \]其中 \(c_i \in \mathbb{Z}, p_i \in \mathbb{P}, p_1 \lt p_2 \lt \cdots \lt p_m\)。
结合试除法和埃氏筛我们就可以进行一个质因数的分解,复杂度为 \(\sqrt{N}\)。
void DivideNum(const int n) {
m = 0;
for (int i = 2; i * i <= n; i++) {
if (n % i == 0) { // i 是质数
p[++m] = i;
c[m] = 0;
while (n % i == 0) { c /= i; c[m]++; } // 抹掉所有 i
}
}
if (n > 1) { p[++m] = n; c[m] = 1; } // n 是质数
}
有一种效率更高的 Pollard's Rho 用于优化复杂度。