本文以十四届蓝桥杯省赛第三题"质因数的个数"为案例,分析了质因数分解的简单模拟法和深度优先搜索法,并进一步对比分析了质数筛选的埃氏算法和欧拉算法,最后给出蓝桥官方视频中的参考代码。所有算法都给出了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到10之间的数中质因数最多的是8,质因数有3个,故输出3。
输入两个正整数N和M(1≤N≤M≤1e7),两个正整数之间用一个空格隔开
输出一个整数,表示质因数个数中的最大值
6 10
3
题目要求统计区间 [N, M]
内每个数的质因数个数,并输出其中最大的值。我们可以利用最小质因数筛法(类似于埃氏筛法)来有效求解。
质因数分解问题:对于每个数,我们可以通过其最小质因数来分解。若一个数 x
是质数,则它的最小质因数是它自身;若它是合数,则它可以被更小的质数整除。
最小质因数筛法:与埃氏筛类似,我们可以提前筛选每个数的最小质因数。利用这一点,可以快速计算出每个数的质因数个数。
统计质因数个数:对于每个数 i
,我们可以通过递归或迭代地将其分解为质因数,直到无法再分解为止,同时统计分解时的质因数个数。
实现步骤:
1
到 M
每个数的质因数个数。[N, M]
,找到其中质因数个数最多的数。下面,我们基于以上思路分别给出C++和Python的模拟算法:
#include
#include
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;
}
数组初始化:vector
用来存储从 1
到 M
每个数的质因数个数,初始时都为 0
。
最小质因数筛法:在循环中,如果 prime_factors_count[i] == 0
,说明 i
是质数。对于 i
的每个倍数 j
,不断用 i
去整除 j
,统计它的质因数个数。
统计最大质因数个数:在遍历完质因数个数后,遍历区间 [N, M]
,找到其中质因数个数最多的那个数。
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))
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),可以在合理的时间内处理 M
最多为 1e7
的情况。
下面使用递归的深度优先搜索(DFS)方法来求解给定整数 n
的质因数分解。以下分别给出C++和Python的代码实现。
#include
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 开始尝试。# 递归函数,执行质因数分解
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
函数:
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
下面给出下埃氏筛选法的C++代码:
#include
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,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开始