题解:AcWing 874 筛法求欧拉函数
【题目来源】
AcWing:874 筛法求欧拉函数 - AcWing题库
【题目描述】
给定一个正整数 \(n\),求 \(1\sim n\) 中每个数的欧拉函数之和。
【输入】
共一行,包含一个整数 \(n\)。
【输出】
共一行,包含一个整数,表示 \(1\sim n\) 中每个数的欧拉函数之和。
【输入样例】
6
【输出样例】
12
【核心思想】
-
问题分析:给定一个正整数 \(n\),求 \(1 \sim n\) 中每个数的欧拉函数之和。若对每个数单独进行质因数分解,总时间将达到 \(O(n \cdot \sqrt{n})\),在 \(n\) 较大时会超时。关键在于利用线性筛法(欧拉筛)在 \(O(n)\) 时间内同时筛出质数并递推计算所有数的欧拉函数值。
-
算法选择:
- 线性筛法:每个合数只被其最小质因数筛一次,时间复杂度 \(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)\)
-
关键步骤:
- 初始化:
phi[1] = 1(\(1\) 与自身互质) - 线性筛主循环(遍历 \(i\) 从 \(2\) 到 \(n\)):
- 若 \(i\) 未被标记(是质数):
primes[cnt++] = iphi[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\) 未被标记(是质数):
- 累加求和:遍历 \(i\) 从 \(1\) 到 \(n\),累加
ans += phi[i] - 输出答案
ans
- 初始化:
-
时间/空间复杂度:
- 时间复杂度:\(O(n)\),线性筛每个合数仅被标记一次,内层循环总体为线性
- 空间复杂度:\(O(n)\),需要
primes、phi、st三个大小为 \(n\) 的数组
-
线性筛求欧拉函数的核心思想:
- 最小质因数保证:每个合数只被其最小质因数筛一次,确保 \(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
浙公网安备 33010602011771号