信奥题解:质数筛选的埃氏算法和欧拉算法

本文以十四届蓝桥杯省赛第三题"质因数的个数"为案例,分析了质因数分解的简单模拟法和深度优先搜索法,并进一步对比分析了质数筛选的埃氏算法和欧拉算法,最后给出蓝桥官方视频中的参考代码。所有算法都给出了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] 内每个数的质因数个数,并输出其中最大的值。我们可以利用最小质因数筛法(类似于埃氏筛法)来有效求解。

  1. 质因数分解问题:对于每个数,我们可以通过其最小质因数来分解。若一个数 x 是质数,则它的最小质因数是它自身;若它是合数,则它可以被更小的质数整除。

  2. 最小质因数筛法:与埃氏筛类似,我们可以提前筛选每个数的最小质因数。利用这一点,可以快速计算出每个数的质因数个数。

  3. 统计质因数个数:对于每个数 i,我们可以通过递归或迭代地将其分解为质因数,直到无法再分解为止,同时统计分解时的质因数个数。

  4. 实现步骤

    • 使用最小质因数筛法,预处理从 1M 每个数的质因数个数。
    • 然后遍历区间 [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++代码解释
  1. 数组初始化vector<int> prime_factors_count(M + 1, 0) 用来存储从 1M 每个数的质因数个数,初始时都为 0

  2. 最小质因数筛法:在循环中,如果 prime_factors_count[i] == 0,说明 i 是质数。对于 i 的每个倍数 j,不断用 i 去整除 j,统计它的质因数个数。

  3. 统计最大质因数个数:在遍历完质因数个数后,遍历区间 [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代码解释
  1. prime_factors_count[i] 用来记录每个数 i 的质因数个数,初始时为0。
  2. 外层循环 for i in range(2, M + 1),如果 prime_factors_count[i] == 0,说明 i 是质数。
  3. 内层循环 for j in range(i, M + 1, i),遍历所有 i 的倍数 j,然后通过除以 i 的方式统计 j 的质因数。
  4. 最后遍历区间 [N, M],找到质因数个数的最大值。

时间复杂度

  • 预处理质因数个数:时间复杂度为 O ( M log ⁡ M ) O(M \log M) O(MlogM),这是由于最小质因数筛法的性质。
  • 遍历区间 [N, M]:时间复杂度为 O ( M − N ) O(M - N) O(MN)

综合来看,时间复杂度约为 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;
}
代码解析:
  1. 函数 dfs(int n, int p)

    • 该函数有两个参数:n(需要分解的数)和 p(当前的因子,初始值为 2)。
    • 递归结束条件:如果 n == 1,函数返回,这意味着质因数分解完成。
    • 递归过程
      • 如果 n % p == 0(即 pn 的因子),则输出 p 并递归调用 dfs(n / p, p) 来继续分解商。
      • 如果 p 不是 n 的因子,则递归调用 dfs(n, p + 1),将因子自增 1,继续检查下一个可能的因子。
  2. 主函数

    • 从用户输入中读取一个整数 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()  # 换行,方便输出整洁
代码解析:
  1. dfs 函数

    • 递归的逻辑和 C++ 版本完全一致,使用 Python 的语法来处理递归。
    • print(p, end=" ") 确保输出在同一行。
  2. 主程序部分

    • 使用 input() 函数读取用户输入,并将其转换为整数 n
    • 调用 dfs(n, 2) 进行质因数分解。

Python 代码更简洁,去除了 C++ 中的类型声明,并且不需要处理头文件的引入。

示例

假设输入 28,函数的执行过程如下:

  • 初始状态为 n = 28p = 2
    • 因为 28 % 2 == 0,输出 2,并调用 dfs(14, 2)
  • 现在 n = 14p = 2
    • 因为 14 % 2 == 0,输出 2,并调用 dfs(7, 2)
  • 现在 n = 7p = 2
    • 因为 7 % 2 != 0,调用 dfs(7, 3)
  • 现在 n = 7p = 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;
}
算法分析
  1. 基本原理

    • 从2开始,将每个质数的倍数标记为合数
    • 最后未被标记的数就是质数
  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. 重要优化
// 优化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开始
  1. 性能分析&#
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值