本文以十四届蓝桥杯省赛第三题"质因数的个数"为案例,分析了质因数分解的简单模拟法和深度优先搜索法,并进一步对比分析了质数筛选的埃氏算法和欧拉算法,最后给出蓝桥官方视频中的参考代码。所有算法都给出了C++和Python两种语言的实现。
题目
来源:十四届蓝桥杯省赛第三题"质因数的个数"
提示信息:
因数:又称为约数,如果整数a除以整数b(b≠0) 的商正好是整数而没有余数,我们就说b是a的因数。
质数:又称为素数,一个大于1的自然数,除了1和它自身外,不能被其他自然数整除的数叫做质数。2是最小的质数。
质因数:如果一个数a的因数b同时也是质数,那么b就是a的一个质因数,例如:8=2×2×2,2就是8的质因数;12=2×2×3,2和3就是12的质因数。
题目描述:
给定两个正整数N和M(1≤N≤M≤1e7),统计N到M之间(含N和M)每个数所包含的质因数的个数,输出其中最大的个数。
例如:
当N=6,M=10,6到10之间
- 6的质因数是2、3,共有2个
- 7的质因数是7,共有1个
- 8的质因数是2、2、2,共有3个
- 9的质因数是3、3,共有2个
- 10的质因数是2、5,共有2个
6到10之间的数中质因数最多的是8,质因数有3个,故输出3。
输入描述:
输入两个正整数N和M(1≤N≤M≤1e7),两个正整数之间用一个空格隔开
输出描述:
输出一个整数,表示质因数个数中的最大值
样例输入:
6 10
样例输出:
3
题目分析
解题思路
题目要求统计区间 [N, M]
内每个数的质因数个数,并输出其中最大的值。我们可以利用最小质因数筛法(类似于埃氏筛法)来有效求解。
-
质因数分解问题:对于每个数,我们可以通过其最小质因数来分解。若一个数
x
是质数,则它的最小质因数是它自身;若它是合数,则它可以被更小的质数整除。 -
最小质因数筛法:与埃氏筛类似,我们可以提前筛选每个数的最小质因数。利用这一点,可以快速计算出每个数的质因数个数。
-
统计质因数个数:对于每个数
i
,我们可以通过递归或迭代地将其分解为质因数,直到无法再分解为止,同时统计分解时的质因数个数。 -
实现步骤:
- 使用最小质因数筛法,预处理从
1
到M
每个数的质因数个数。 - 然后遍历区间
[N, M]
,找到其中质因数个数最多的数。
- 使用最小质因数筛法,预处理从
简单模拟法
下面,我们基于以上思路分别给出C++和Python的模拟算法:
C++代码实现
#include <iostream>
#include <vector>
using namespace std;
// 使用最小质因数筛法计算每个数的质因数个数
int max_prime_factors_count(int N, int M) {
// 创建一个大小为 M+1 的数组,用于存储每个数的质因数个数
vector<int> prime_factors_count(M + 1, 0);
// 筛选过程,计算每个数的质因数个数
for (int i = 2; i <= M; ++i) {
if (prime_factors_count[i] == 0) {
// 如果 prime_factors_count[i] 为 0,说明 i 是质数
for (int j = i; j <= M; j += i) {
int temp = j;
while (temp % i == 0) {
prime_factors_count[j]++;
temp /= i;
}
}
}
}
// 找到区间 [N, M] 内质因数个数最多的数
int max_count = 0;
for (int i = N; i <= M; ++i) {
max_count = max(max_count, prime_factors_count[i]);
}
return max_count;
}
int main() {
int N, M;
// 输入两个正整数 N 和 M
cin >> N >> M;
// 输出区间 [N, M] 中质因数个数的最大值
cout << max_prime_factors_count(N, M) << endl;
return 0;
}
C++代码解释
-
数组初始化:
vector<int> prime_factors_count(M + 1, 0)
用来存储从1
到M
每个数的质因数个数,初始时都为0
。 -
最小质因数筛法:在循环中,如果
prime_factors_count[i] == 0
,说明i
是质数。对于i
的每个倍数j
,不断用i
去整除j
,统计它的质因数个数。 -
统计最大质因数个数:在遍历完质因数个数后,遍历区间
[N, M]
,找到其中质因数个数最多的那个数。
Python代码实现
def max_prime_factors_count(N, M):
# 初始化一个长度为 M+1 的数组,用来存储每个数的质因数个数
prime_factors_count = [0] * (M + 1)
# 使用最小质因数筛法来计算每个数的质因数个数
for i in range(2, M + 1):
if prime_factors_count[i] == 0: # 如果 prime_factors_count[i] 为 0,说明 i 是质数
for j in range(i, M + 1, i):
# 对于 j,i 是它的一个质因数,增加质因数的计数
temp = j
while temp % i == 0:
prime_factors_count[j] += 1
temp //= i
# 遍历区间 [N, M],找到质因数最多的那个数
max_count = 0
for i in range(N, M + 1):
max_count = max(max_count, prime_factors_count[i])
return max_count
# 输入部分
N, M = map(int, input().split())
# 输出结果
print(max_prime_factors_count(N, M))
Python代码解释
prime_factors_count[i]
用来记录每个数i
的质因数个数,初始时为0。- 外层循环
for i in range(2, M + 1)
,如果prime_factors_count[i] == 0
,说明i
是质数。 - 内层循环
for j in range(i, M + 1, i)
,遍历所有i
的倍数j
,然后通过除以i
的方式统计j
的质因数。 - 最后遍历区间
[N, M]
,找到质因数个数的最大值。
时间复杂度
- 预处理质因数个数:时间复杂度为 O ( M log M ) O(M \log M) O(MlogM),这是由于最小质因数筛法的性质。
- 遍历区间 [N, M]:时间复杂度为 O ( M − N ) O(M - N) O(M−N)。
综合来看,时间复杂度约为 O ( M log M ) O(M \log M) O(MlogM),可以在合理的时间内处理 M
最多为 1e7
的情况。
深度优先搜索
下面使用递归的深度优先搜索(DFS)方法来求解给定整数 n
的质因数分解。以下分别给出C++和Python的代码实现。
C++代码实现
#include <iostream>
using namespace std;
// 递归函数,执行质因数分解
void dfs(int n, int p) {
// 递归结束条件:当 n 等于 1 时,分解结束
if (n == 1) return;
// 如果 p 是 n 的因子
if (n % p == 0) {
cout << p << " "; // 输出因子 p
dfs(n / p, p); // 继续分解 n/p
} else {
dfs(n, p + 1); // 如果 p 不是因子,尝试下一个因子
}
}
int main() {
int n;
cin >> n; // 读取用户输入的整数 n
// 开始从因子 2 开始递归分解 n
dfs(n, 2);
cout << endl; // 换行,方便输出整洁
return 0;
}
代码解析:
-
函数
dfs(int n, int p)
:- 该函数有两个参数:
n
(需要分解的数)和p
(当前的因子,初始值为 2)。 - 递归结束条件:如果
n == 1
,函数返回,这意味着质因数分解完成。 - 递归过程:
- 如果
n % p == 0
(即p
是n
的因子),则输出p
并递归调用dfs(n / p, p)
来继续分解商。 - 如果
p
不是n
的因子,则递归调用dfs(n, p + 1)
,将因子自增 1,继续检查下一个可能的因子。
- 如果
- 该函数有两个参数:
-
主函数:
- 从用户输入中读取一个整数
n
。 - 调用
dfs(n, 2)
开始质因数分解,从因子 2 开始尝试。
- 从用户输入中读取一个整数
Pyhton代码实现
# 递归函数,执行质因数分解
def dfs(n, p):
# 递归结束条件:当 n 等于 1 时,分解结束
if n == 1:
return
# 如果 p 是 n 的因子
if n % p == 0:
print(p, end=" ") # 输出因子 p,并保持在同一行
dfs(n // p, p) # 继续分解 n/p
else:
dfs(n, p + 1) # 如果 p 不是因子,尝试下一个因子
# 主函数
if __name__ == "__main__":
n = int(input("请输入一个整数: ")) # 读取用户输入的整数 n
# 开始从因子 2 开始递归分解 n
dfs(n, 2)
print() # 换行,方便输出整洁
代码解析:
-
dfs
函数:- 递归的逻辑和 C++ 版本完全一致,使用 Python 的语法来处理递归。
print(p, end=" ")
确保输出在同一行。
-
主程序部分:
- 使用
input()
函数读取用户输入,并将其转换为整数n
。 - 调用
dfs(n, 2)
进行质因数分解。
- 使用
Python 代码更简洁,去除了 C++ 中的类型声明,并且不需要处理头文件的引入。
示例
假设输入 28
,函数的执行过程如下:
- 初始状态为
n = 28
,p = 2
。- 因为
28 % 2 == 0
,输出2
,并调用dfs(14, 2)
。
- 因为
- 现在
n = 14
,p = 2
。- 因为
14 % 2 == 0
,输出2
,并调用dfs(7, 2)
。
- 因为
- 现在
n = 7
,p = 2
。- 因为
7 % 2 != 0
,调用dfs(7, 3)
。
- 因为
- 现在
n = 7
,p = 3
。- 因为
7 % 3 != 0
,调用dfs(7, 4)
,依此类推,直到p = 7
时输出7
并返回。
- 因为
对于输入 n = 28
,输出结果为:
2 2 7
注意事项
- 这种递归方法可以有效地求出质因数分解。
- 但是对于较大的数,这种递归方式可能效率不高,因为因子逐一递增,检查次数较多。更高效的方法可以使用埃拉托色尼筛法(Sieve of Eratosthenes)预先计算质数,或者通过迭代而非递归来优化性能。
埃拉托色尼筛法(Sieve of Eratosthenes)
下面给出下埃氏筛选法的C++代码:
C++代码实现
#include <iostream>
using namespace std;
const int N = 1e6 + 10;
void eratosthenes() {
bool isNotPrime[N] = {
false};
int primes[N], cnt = 0;
isNotPrime[0] = isNotPrime[1] = true;
for (int i = 2; i < N; i++) {
if (!isNotPrime[i]) {
primes[cnt++] = i;
for (long long j = (long long)i * i; j < N; j += i) {
isNotPrime[j] = true;
}
}
}
// 输出前100个质数
for (int i = 0; i < min(100, cnt); i++) {
cout << primes[i] << " ";
}
}
int main() {
eratosthenes();
return 0;
}
算法分析
-
基本原理:
- 从2开始,将每个质数的倍数标记为合数
- 最后未被标记的数就是质数
-
具体步骤:
// 以范围[2,20]为例说明筛选过程:
// 初始状态:2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
// 第一轮:2是质数,标记2的倍数(4,6,8,10,12,14,16,18,20)
// 剩余:2 3 5 7 9 11 13 15 17 19
// 第二轮:3是质数,标记3的倍数(9,15)
// 剩余:2 3 5 7 11 13 17 19
// 第三轮:5是质数,但5²=25已超出范围,结束
// 最终得到质数:2 3 5 7 11 13 17 19
- 重要优化:
// 优化1:从i*i开始标记
for (long long j = (long long)i * i; j < N; j += i)
// 而不是从2*i开始
// for (int j = 2 * i; j < N; j += i)
// 原因:小于i的倍数已经被之前的质数标记过了
// 例如:标记2的倍数时已经标记了4,6,8...
// 标记3时不需要从6开始,可以直接从9开始
- 性能分析&#