数据结构与算法学习笔记----欧拉函数
@@ author: 明月清了个风
@@ first publish time: 2025.1.1ps⭐️欧拉函数的定义及求法,第二题是在线性筛法的过程中维护欧拉函数。
欧拉函数
通常用符号 φ ( n ) \varphi(n) φ(n)表示,定义为小于或等于 n n n且与 n n n互质的正整数的个数。
如何求解
若 n n n的质因数分解为(基于算术基本定理,这里可以看上一篇约数的相关内容):
n = p 1 e 1 p 2 e 2 ⋯ p k e k (1) n=p_{1}^{e_1}p_{2}^{e_2}\cdots p_{k}^{e_k} \tag{1} n=p1e1p2e2⋯pkek(1)
则欧拉函数 φ ( n ) \varphi(n) φ(n)可以通过以下公式计算:
φ ( n ) = n ( 1 − 1 p 1 ) ( 1 − 1 p 2 ) ⋯ ( 1 − 1 p k ) (2) \varphi(n) = n (1 - \frac{1}{p_1}) (1 - \frac{1}{p_2}) \cdots (1 - \frac{1}{p_k}) \tag{2} φ(n)=n(1−p11)(1−p21)⋯(1−pk1)(2)
公式推导
这里给出y总讲解的公式推导,根据容斥原理进行理解。
对于每个质因数 p i p_i pi来说, 1 ∼ n 1 \sim n 1∼n中有 n p i \frac{n}{p_i} pin个数与 n n n不互斥,这些分别是 p i p_i pi的 1 1 1倍, 2 2 2倍…。
那么就要从 n n n中减去这些数,因此有
n − n p 1 − n p 2 − ⋯ − n p k (3) n - \frac{n}{p_1} - \frac{n}{p_2} - \cdots -\frac{n}{p_k} \tag{3} n−p1n−p2n−⋯−pkn(3)
但是需要注意的是,在 1 ∼ n 1 \sim n 1∼n中也会有一些数既是 p i p_i pi的倍数又是 p j p_j pj的倍数,那么这个数就会被减去两次,因此又要加回来,上式变为
n − n p 1 − n p 2 − ⋯ − n p k + ∑ 1 p i p j (4) n - \frac{n}{p_1} - \frac{n}{p_2} - \cdots -\frac{n}{p_k} + \sum \frac{1}{p_i p_j} \tag{4} n−p1n−p2n−⋯−pkn+∑pipj1(4)
以此类推,又会有有的数是三个质因数的倍数,因此又会多加上,所以要再减去
最终就会有
n − n p 1 − n p 2 − ⋯ − n p k + ∑ 1 p i p j − ∑ 1 p i p j p k + ⋯ ± ∑ 1 p i p j ⋯ p k (5) n - \frac{n}{p_1} - \frac{n}{p_2} - \cdots -\frac{n}{p_k} + \sum \frac{1}{p_i p_j} - \sum \frac{1}{p_i p_j p_k} + \cdots \pm \sum \frac{1}{p_i p_j \cdots p_k} \tag{5} n−p1n−p2n−⋯−pkn+∑pipj1−∑pipjpk1+⋯±∑pipj⋯pk1(5)
即式(2)的展开式。
Acwing 873. 欧拉函数
[原题链接](873. 欧拉函数 - AcWing题库)
给定 n n n个正整数 a i a_i ai,对于每个整数 a i a_i ai,请你求出每个数的欧拉函数。
输入格式
第一行包含整数 n n n,
接下来 n n n行,每行包含一个正整数 a i a_i ai。
输出格式
输出 n n n行,每行输出一个正整数 a i a_i ai的欧拉函数
数据范围
1 ≤ n ≤ 100 1 \le n \le 100 1≤n≤100,
1 ≤ a i ≤ 2 × 1 0 9 1 \le a_i \le 2\times 10^9 1≤ai≤2×109、
思路
按照上述公式,对每个 a i a_i ai分解质因数即可,然后按式(2)乘起来,注意点是不要有小数,因此要变形一下,先除再乘,具体看代码的实现吧。
代码的时间复杂度瓶颈就在分解质因数这一步,因为要算 n n n次,所以为 O ( n n ) O(n\sqrt{n}) O(nn)。
代码
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
int main()
{
int n;
cin >> n;
while(n --)
{
int a;
cin >> a;
int res = a;
for(int i = 2; i <= a / i; i ++)
{
if(a % i == 0) // 分解质因数
{
res = res / i * (i - 1);
while(a % i == 0) a /= i;
}
}
if(a > 1) res = res / a * (a - 1);
cout << res << endl;
}
return 0;
}
Acwing 874. 筛法求欧拉函数
[原题链接](874. 筛法求欧拉函数 - AcWing题库)
给定一个正整数 n n n,求 1 ∼ n 1 \sim n 1∼n中每个数的欧拉函数之和。
输入格式
共一行,包含一个整数 n n n。
输出格式
共一行,包含一个整数,表示 1 ∼ n 1 \sim n 1∼n中每个数的欧拉函数之和 。
数据范围
1 ≤ n ≤ 1 0 6 1 \le n \le 10^6 1≤n≤106
思路
线性筛选的过程中可以维护欧拉函数,关于线性筛法的讲解可以看这一个链接,这里我们放一个线性筛的代码
int primes[N], cnt;
bool st[N];
void get_primes(int n)
{
for(int i = 2; i <= n; i ++)
{
if(!st[i]) primes[cnt ++] = i;
for(int j = 0; primes[j] <= n / i; j ++)
{
st[i * primes[j]] = true;
if(i % primes[j] == 0) break;
}
}
}
首先根据定义,所有的质数 i i i的欧拉函数是 φ ( i ) = i − 1 \varphi(i) = i - 1 φ(i)=i−1,因为 1 ∼ i 1 \sim i 1∼i中有 i − 1 i - 1 i−1个数与质数 i i i自己互质。
在线性筛中,我们会将所有的质数都存在primes[]
数组中,对于所有的合数通过st[]
数组标记的方式筛选,那么对于这些数来说,有两种情况:1️⃣i %primes[j] != 0
;2️⃣i % primes[[j] == 0
。
- 线性筛的原则是对于每个非质数,用其最小质因子将其筛除,那么对于第一种情况,
primes[j]
不是i
的质因子且大于i
的所有质因子(因为是从小达到枚举的),那么对于数i * primes[j]
来说,primes[j]
就是其最小质因子,因此这里st[i * primes[j] = true
将其筛除,那么其质因数分解就会是i
的质因数分解再乘上primes[j]
,根据欧拉函数的定义,就可以知道其欧拉函数表达式为 φ ( i ∗ p r i m e s [ j ] ) = φ ( i ) ∗ p r i m e s [ j ] ∗ ( 1 − 1 p r i m e s [ j ] ) \varphi(i * primes[j]) = \varphi(i) * primes[j] * (1 - \frac{1}{primes[j]}) φ(i∗primes[j])=φ(i)∗primes[j]∗(1−primes[j]1),用代码表示就是phi[i * primes[j] = phi[i] * (primes[j] - 1)
。 - 那么对于第二种情况,当
i % primes[j] == 0
时表示primes[j]
是i
的最小质因子,因此因此其欧拉函数表达式为 φ ( i ∗ p r i m e s [ j ] ) = φ ( i ) ∗ p r i m e s [ j ] \varphi(i * primes[j]) = \varphi(i) * primes[j] φ(i∗primes[j])=φ(i)∗primes[j],用代码表示就是phi[i * primes[j] = phi[i] * primes[j]
。
代码
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 1000010;
int n;
int phi[N];
int primes[N], cnt;
bool st[N];
LL get_eulers(int n)
{
phi[1] = 1;
for(int i = 2; i <= n; i ++)
{
if(!st[i]) primes[cnt ++] = i, phi[i] = i - 1;
for(int j = 0; primes[j] <= n / i; j ++)
{
st[i * primes[j]] = true;
if(i % primes[j] == 0)
{
phi[i * primes[j]] = phi[i] * primes[j];
break;
}
phi[i * primes[j]] = phi[i] * (primes[j] - 1);
}
}
LL res = 0;
for(int i = 1; i <= n; i ++) res += phi[i];
return res;
}
int main()
{
cin >> n;
cout << get_eulers(n) << endl;
return 0;
}