题解:AcWing 874 筛法求欧拉函数

【题目来源】

AcWing:874 筛法求欧拉函数 - AcWing题库

【题目描述】

给定一个正整数 \(n\),求 \(1\sim n\) 中每个数的欧拉函数之和。

【输入】

共一行,包含一个整数 \(n\)

【输出】

共一行,包含一个整数,表示 \(1\sim n\) 中每个数的欧拉函数之和。

【输入样例】

6

【输出样例】

12

【核心思想】

  1. 问题分析:给定一个正整数 \(n\),求 \(1 \sim n\) 中每个数的欧拉函数之和。若对每个数单独进行质因数分解,总时间将达到 \(O(n \cdot \sqrt{n})\),在 \(n\) 较大时会超时。关键在于利用线性筛法(欧拉筛)\(O(n)\) 时间内同时筛出质数并递推计算所有数的欧拉函数值。

  2. 算法选择

    • 线性筛法:每个合数只被其最小质因数筛一次,时间复杂度 \(O(n)\)
    • 欧拉函数递推公式:在筛的过程中利用已求出的欧拉函数值递推新合数的欧拉函数
      • \(i\) 是质数:\(\varphi(i) = i - 1\)
      • \(i \bmod p_j = 0\)\(p_j\)\(i\) 的最小质因数):\(\varphi(i \cdot p_j) = \varphi(i) \cdot p_j\)
      • \(i \bmod p_j \neq 0\)\(p_j\) 不是 \(i\) 的因数):\(\varphi(i \cdot p_j) = \varphi(i) \cdot (p_j - 1)\)
  3. 关键步骤

    • 初始化phi[1] = 1\(1\) 与自身互质)
    • 线性筛主循环(遍历 \(i\)\(2\)\(n\)):
      • \(i\) 未被标记(是质数):
        • primes[cnt++] = i
        • phi[i] = i - 1
      • 遍历质数表 \(p_j\)(直到 \(p_j \cdot i > n\)):
        • 标记 st[p_j * i] = true
        • 情况一:若 \(i \bmod p_j == 0\)
          • \(p_j\)\(i\) 的最小质因数,\(i\)\(p_j \cdot i\) 拥有相同的质因数集合(只是 \(p_j\) 的指数多 \(1\)
          • 由欧拉函数公式 \(\varphi(p^k \cdot m) = p^k \cdot \varphi(m)\)(当 \(p \nmid m\)),可推出 \(\varphi(i \cdot p_j) = \varphi(i) \cdot p_j\)
          • break(保证每个合数只被最小质因数筛一次)
        • 情况二:若 \(i \bmod p_j \neq 0\)
          • \(p_j\)\(i\) 互质,利用欧拉函数的乘性:\(\varphi(i \cdot p_j) = \varphi(i) \cdot \varphi(p_j) = \varphi(i) \cdot (p_j - 1)\)
    • 累加求和:遍历 \(i\)\(1\)\(n\),累加 ans += phi[i]
    • 输出答案 ans
  4. 时间/空间复杂度

    • 时间复杂度:\(O(n)\),线性筛每个合数仅被标记一次,内层循环总体为线性
    • 空间复杂度:\(O(n)\),需要 primesphist 三个大小为 \(n\) 的数组
  5. 线性筛求欧拉函数的核心思想

    • 最小质因数保证:每个合数只被其最小质因数筛一次,确保 \(O(n)\) 的时间复杂度
    • 递推而非分解:不单独对每个数做质因数分解,而是在筛的过程中利用已知的 \(\varphi(i)\) 和质数 \(p_j\) 的关系直接递推出 \(\varphi(i \cdot p_j)\)
    • 两种递推情况
      • \(p_j | i\) 时,\(i\)\(i \cdot p_j\) 的互质数集合结构相同,只是比例缩放,故 \(\varphi(i \cdot p_j) = \varphi(i) \cdot p_j\)
      • \(p_j \nmid i\) 时,\(p_j\)\(i\) 互质,利用乘性性质 \(\varphi(i \cdot p_j) = \varphi(i) \cdot (p_j - 1)\)
    • 欧拉函数求和的应用\(\sum_{i=1}^{n} \varphi(i)\) 在数论中有重要意义,例如与法里级数、可见格点等问题密切相关
    • 适用于大范围欧拉函数批量计算、与质数筛结合的问题

【解题思路】

【算法标签】

欧拉定理

【代码详解】

#include <bits/stdc++.h>
using namespace std;

const int N = 1000005; // 定义常量 N,表示最大范围
int primes[N], cnt; // primes 数组存储质数,cnt 表示质数的数量
int phi[N]; // phi 数组存储欧拉函数值
bool st[N]; // st 数组标记是否为合数
long long ans = 0; // ans 用于存储欧拉函数的累加和

// 筛法求质数并计算欧拉函数
void get_primes(int n)
{
    phi[1] = 1; // 初始化 phi[1] = 1
    for (int i = 2; i <= n; i++) { // 遍历 2 到 n 的所有整数
        if (st[i] == false) { // 如果 i 是质数
            primes[cnt++] = i; // 将 i 加入质数数组
            phi[i] = i - 1; // 质数的欧拉函数值为 i - 1
        }
        for (int j = 0; primes[j] <= n / i; j++) { // 筛去 i 的倍数
            st[primes[j] * i] = true; // 标记 primes[j] * i 为合数
            if (i % primes[j] == 0) { // 如果 i 能被 primes[j] 整除
                phi[primes[j] * i] = phi[i] * primes[j]; // 更新欧拉函数值
                break; // 跳出循环
            }
            phi[primes[j] * i] = phi[i] * (primes[j] - 1); // 更新欧拉函数值
        }
    }
    for (int i = 1; i <= n; i++) ans += phi[i]; // 累加欧拉函数值
}

int main()
{
    int n; // 定义整数 n
    cin >> n; // 输入整数 n
    get_primes(n); // 调用筛法求质数并计算欧拉函数
    cout << ans << endl; // 输出欧拉函数的累加和
    return 0; // 程序结束
}
#include <bits/stdc++.h>
using namespace std;

typedef long long LL;  // 定义长整型别名,防止溢出
const int N = 1000005;  // 数组大小

int primes[N], cnt;    // primes[]存储质数,cnt是质数个数
int phi[N];            // phi[i]存储φ(i)的值
bool st[N];            // st[i]标记i是否是合数,true表示是合数

// 线性筛法计算1~n的欧拉函数,并返回欧拉函数之和
LL get_eulers(int n)
{
    phi[1] = 1;  // φ(1) = 1

    for (int i = 2; i <= n; i++)
    {
        if (!st[i])  // 如果i是质数
        {
            primes[cnt++] = i;  // 将i加入质数表
            phi[i] = i - 1;     // 质数的欧拉函数值为i-1
        }

        // 用当前已得到的质数筛掉合数
        for (int j = 0; primes[j] * i <= n; j++)
        {
            st[primes[j] * i] = true;  // 标记primes[j]*i为合数

            if (i % primes[j] == 0)  // 如果primes[j]是i的最小质因数
            {
                // 此时primes[j]*i和i有相同的质因数集合
                // φ(primes[j]*i) = primes[j] * φ(i)
                phi[primes[j] * i] = phi[i] * primes[j];
                break;  // 关键:保证每个合数只被最小的质因数筛掉
            }
            else  // 如果primes[j]不是i的质因数
            {
                // 此时primes[j]和i互质
                // φ(primes[j]*i) = φ(primes[j]) * φ(i) = (primes[j]-1) * φ(i)
                phi[primes[j] * i] = phi[i] * (primes[j] - 1);
            }
        }
    }

    LL res = 0;  // 计算1~n的欧拉函数之和
    for (int i = 1; i <= n; i++)
    {
        res += phi[i];
    }
    return res;
}

int main()
{
    int n;  // 输入的上界
    cin >> n;

    cout << get_eulers(n) << endl;  // 输出1~n的欧拉函数之和

    return 0;
}

【运行结果】

6
12
posted @ 2026-06-23 11:24  团爸讲算法  阅读(8)  评论(0)    收藏  举报