数论·质数
质数 vs 合数
数学定义
- 0和1不是质数也不是合数
- 大于等于2且公因子只有1和它本身的数
判定质数
试除法: O ( n ) O(\sqrt{n}) O(n)
数学原理
这个判定质数的方法基于一个重要的数学原理:
- 如果一个数 n n n 是合数,它一定有一个小于等于 n \sqrt{n} n 的因子。
- 如果一个数 n n n 是合数,它至多有一个大于 n \sqrt{n} n 的因子
举例验证
- 26=2*13属于合数
- 2满足定理1,13满足定理2
实现
- 注意小于2的都不是质数。
- 判断条件使用
i<x/i等价于i*i<x,但是前者不容易溢出。
bool isprime(int x) {
if (x < 2)return false;
for (int i = 2; i <= x / i; i++) {
if (x % i == 0)return false;
}
return true;
}
质因数分解
试除法: O ( l o g 2 n ) − O ( n ) O(log_2^n)-O(\sqrt{n}) O(log2n)−O(n)
数学定义
定理(算术基本定理)
任意大于 1 的整数 x x x 都可以唯一地表示为若干个质数的乘积:
x = p 1 α 1 ⋅ p 2 α 2 ⋯ p k α k x = p_1^{\alpha_1} \cdot p_2^{\alpha_2} \cdots p_k^{\alpha_k} x=p1α1⋅p2α2⋯pkαk
其中 p 1 < p 2 < ⋯ < p k p_1 < p_2 < \cdots < p_k p1<p2<⋯<pk 是质数, α i ∈ N + \alpha_i \in \mathbb{N}^+ αi∈N+。
注意:该定义同时适用于素数和合数
实现
- 如果当前数可以被整除,则可以一直整除得到因子和对应指数。
- 该算法保证:没有一个因子为合数。证明:如果有一个因子是合数,那么在算法处理过程中,该合数的因子率先被处理,不可能留下该合数。
- 特判:如果是质数,那么前面的循环完全无效,因此需要特判
void divide(int x) {
for (int i = 2; i <= x / i; i++) {
if (x % i == 0) {
int s = 0;
cout << i << " ";
while (x % i == 0) {
s++;
x /= i;
}
cout << s << endl;
}
}
if (x > 1)cout << x << " " << 1 << endl;
cout << endl;
}
筛选质数
筛选的理解
删除合数,保留质数
暴力筛选 O ( n l o g n ) O(nlogn) O(nlogn)
实现
- 对于每一个数(2-n-1),将其倍数都标记为合数
- 问题:出现了例如2和4会重复对4进行筛选的问题。
埃式筛: O ( n l o g l o g n ) O(nloglogn) O(nloglogn)
实现
- 对于每一个质数(2,3,5…)进行筛选,2-n中质数的数量为logn,可以有效降低筛选次数
- 问题:仍然出现重复筛选,例如2和5都对10进行了筛选,这是不必要的。
void getprimes() {
for (int i = 2; i <= n; i++) {
if (isprimes[i]) {
primes.push_back(i);
for (int j = i + i; j <= n; j+=i) {
isprimes[j] = 0;
}
}
}
}
欧拉筛: O ( n ) O(n) O(n)
数学原理
其核心思想是:确保每个合数只被它的最小质因数标记一次。
根据算术基本定理,任意合数 n n n 可以唯一地表示为:
n = p 1 α 1 ⋅ p 2 α 2 ⋯ p k α k n = p_1^{\alpha_1} \cdot p_2^{\alpha_2} \cdots p_k^{\alpha_k} n=p1α1⋅p2α2⋯pkαk
其中 p 1 < p 2 < ⋯ < p k p_1 < p_2 < \cdots < p_k p1<p2<⋯<pk 是质数, α i ∈ N + \alpha_i \in \mathbb{N}^+ αi∈N+。
特别地, n n n 可以写成:
n = p min ⋅ m n = p_{\min} \cdot m n=pmin⋅m
其中 p min p_{\min} pmin 是 n n n 的最小质因数,且 m > p min m > p_{\min} m>pmin(因为如果 m ≤ p min m \le p_{\min} m≤pmin,则 m m m 会有更小的质因数)。
对于合数 n = p min ⋅ m n = p_{\min} \cdot m n=pmin⋅m,必然有:
p min ≤ m p_{\min} \le m pmin≤m
- 若 p min > m p_{\min} > m pmin>m,则 m m m 的最小质因数会小于 p min p_{\min} pmin,矛盾。
这意味着在欧拉筛的过程中,当用 i i i 遍历时,对每个质数 p j p_j pj:
- 若 p j ∣ i p_j \mid i pj∣i,则 p j p_j pj 是 i i i 的最小质因数
- 此时 i ⋅ p j i \cdot p_j i⋅pj 的最小质因数就是 p j p_j pj,应该被标记
- 但 i ⋅ p j + 1 i \cdot p_{j+1} i⋅pj+1 的最小质因数仍然是 p j p_j pj(因为 p j < p j + 1 p_j < p_{j+1} pj<pj+1 且 p j ∣ i p_j \mid i pj∣i),所以不应该由 p j + 1 p_{j+1} pj+1 标记
实现
- 确保
i % primes[j] == 0时停止,因为i=primes[j]*k,而primes[j+1]*i=primes[j+1]*primes[j]*k,最小因子一定是prime[j],不符合欧拉筛的标准。
void getprimes() {
for (int i = 2; i <= n; i++) {
if (isprimes[i]) {
primes.push_back(i);
}
// 合数也要参会筛选
for (int j = 0; j < primes.size(); j++) {
if (i * primes[j] > n)break;
isprimes[i * primes[j]] = 0;
if (i % primes[j] == 0)break;
}
}
}
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐


所有评论(0)