质数
定义
质数,是一个只能被 \(1\) 和它自身整除的自然数,且大于 \(1\) 。简单来说,质数是一个大于 \(1\) 的整数,除了 \(1\) 和它自己以外,不能被其他任何数整除。
分布
\(N\) 以内质数个数:
即每 \(\ln N\) 个数中就有可能出现一个质数。
所以在 \(x\le10^{14}\) 时,暴力寻找比 \(x\) 大(小)的第一个质数时可在一秒内查询到
判定
试除法
不难发现一个数的因子是成对出现的,即若 \(d_1\ |\ x\ (d_1\ge\sqrt x)\) ,则有 \(d_2 = \large{\frac{x}{d_1}}\le\sqrt x\)
所以我们只需要扫描 \(2\sim \sqrt N\) 的所有整数 \(x\) ,若 \(\forall\ x\ \nmid N\) 则 \(N\) 是质数,否则 \(N\) 是合数
代码
bool is_prime(int n) {
if (n < 2) return false;
for (int i = 2; i * i <= n; i++)
if (!(n % i))
return false;
return true;
}
筛法
\(\text{Eratosthenes}\) 筛法
\(\text{Eratosthenes}\) 筛法通过反复标记合数(非质数)来筛选出质数。这个算法的时间复杂度为 \(\mathcal{O}(n \log \log n)\) 。
在 \(n\le 10^7\) 埃氏筛时间复杂度在 \(\mathcal{O}(4\times10^7)\) 左右,埃氏筛可以处理,但是常数过大时可能 TLE
代码
void primes(int n) {
memset(v, 0, sizeof(v)); // 合数标记
for (int i = 2; i <= n; i++) {
if (v[i]) continue;
cout << i << '\n'; // i 是质数
for (int j = 1; j <= n / i; j++) v[i * j] = 1;
}
}
\(\text{Euler}\) 筛法(欧拉筛法或线性筛法)
不难发现 \(\text{Eratosthenes}\) 筛法仍然会重复标记合数,例如 \(12\) 会被 \(2\) 与 \(3\) 标记,在标记 \(2\) 的倍数时, \(12 = 2 \times 6\) ,在标记 \(3\) 的倍数时, \(12 = 3 \times 4\) 。本质是因为我们无法用确定的方式标记 \(12\) ,所以我们尝试只用每个数的最小质因子标记,即让 \(12\) 只被 \(2\) 标记。
线性筛法通过“从小到大累积质因子”的方式标记合数。设数组 \(v\) 记录每个数的最小质因子,我们通过以下方式维护 \(v\) 。
- 依次考虑 \(2\sim N\) 之间的每一个数 \(i\) 。
- 若 \(v_i = 0\) ,说明 \(i\) 是质数,把它记录下来。
- 扫描不大于 \(v_i\) 的每个质数 \(p\) ,令 \(v_{i\times p} = p\) 。
算法的关键在于步骤 \(3\) 中的不大于,这样可使任意的 \(i\) 只被它的最小质因子标记,可以尝试用反证法证明。
证明:
假设当前枚举到 \(i\) , \(i\) 的最小质因子为 \(v_i\) , \(j > v_i\) 。
当我们尝试标记 \(i \times j\) 时, \(i \times j = v_i \times \large\frac{i}{v_i}\) ,此时就不满足被 \(i\times j\) 的最小质因子标记。证毕
代码
for (int i = 2; i <= n; i++) {
if (!v[i]) v[i] = i, primes.push_back(i); // i 是质数
// 给当前的数 i 乘上一个最小质因子
for (int p : primes) {
// i 有比 p 更小的质因子,或者超出 n 的范围 停止循环
if (p > v[i] || p * i > n) break;
// p 是合数 i * p 的最小质因子
v[i * p] = p;
}
}
质因数分解
算数基本定理
任何一个大于 \(1\) 的正整数 \(N\) 都能唯一分解为有限个质数的乘积,可写作:
其中:\(c_i \in \mathbb{N}_+\) , \(p_i\) 都是质数,并且满足 \(p_1 < p_2 < \cdots<p_m\) 。
由此可得: \(N\) 至多只有一个大于 \(\sqrt N\) 的质因子,考虑反证法。
证明:
设 \(p_i,\ p_j\) 分别为 \(N\) 的两个不同的质因子且 $p_i>\sqrt N,\ p_j>\sqrt N $ 。
所以 \(p_i\times p_j > N\) , 与 \(p_i^{c_i}\times p_j^{c_j}\le N\) 矛盾。证毕
试除法
试除法 是一种用于分解正整数为质因数乘积的基本方法。基本思路是:
- 从最小的质数开始 ,逐一尝试除以待分解的整数。
- 如果当前质数能整除该整数,则记录下该质数,并将整数除以该质数。
- 重复上述过程,直到待分解的整数为 1。
不难发现,进行以上算法步骤需要从 \(2\sim n - 1\) 进行试除,效率较低。
已知大于 \(\sqrt n\) 的质因子至多只有一个,所以可通过下面的方式优化:
- 只检查到 \(\sqrt n\)
代码
void divide(int n) {
m = 0;
for (int i = 2; i * i <= n; i++)
if (!(n % i)) { // i 是质数
p[++m] = i, c[m] = 0;
while (!(n % i)) 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] << '\n';
}