质数 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α1p2α2pkαk

其中 p 1 < p 2 < ⋯ < p k p_1 < p_2 < \cdots < p_k p1<p2<<pk 是质数, α i ∈ N + \alpha_i \in \mathbb{N}^+ αiN+

注意:该定义同时适用于素数和合数

实现

  • 如果当前数可以被整除,则可以一直整除得到因子和对应指数
  • 该算法保证:没有一个因子为合数。证明:如果有一个因子是合数,那么在算法处理过程中,该合数的因子率先被处理,不可能留下该合数。
  • 特判:如果是质数,那么前面的循环完全无效,因此需要特判
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α1p2α2pkαk
其中 p 1 < p 2 < ⋯ < p k p_1 < p_2 < \cdots < p_k p1<p2<<pk 是质数, α i ∈ N + \alpha_i \in \mathbb{N}^+ αiN+

特别地, n n n 可以写成:
n = p min ⁡ ⋅ m n = p_{\min} \cdot m n=pminm
其中 p min ⁡ p_{\min} pmin n n n 的最小质因数,且 m > p min ⁡ m > p_{\min} m>pmin(因为如果 m ≤ p min ⁡ m \le p_{\min} mpmin,则 m m m 会有更小的质因数)。


对于合数 n = p min ⁡ ⋅ m n = p_{\min} \cdot m n=pminm,必然有:
p min ⁡ ≤ m p_{\min} \le m pminm

  • 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 pji,则 p j p_j pj i i i 的最小质因数
  • 此时 i ⋅ p j i \cdot p_j ipj 的最小质因数就是 p j p_j pj,应该被标记
  • i ⋅ p j + 1 i \cdot p_{j+1} ipj+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 pji),所以不应该由 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;
		}
	}
}
Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐