1.求欧拉函数
欧拉函数phi(n)
:得到1~n中与n互质的数的个数
例如:phi(6) = 2,1 - 6 中与 6 互质的数为 1、5
a,b互质就是gcd(a,b) = 1
如何求解欧拉函数?
对于一个数N,可以分解质因数为
N
=
P
1
k
1
×
P
2
k
2
×
⋯
×
P
n
k
n
,则
p
h
i
(
N
)
=
N
×
(
1
−
1
P
1
)
×
(
1
−
1
P
2
)
×
⋯
×
(
1
−
1
P
n
)
N = P_{1}^{k_{1}} \times P_{2}^{k_{2}} \times \dots \times P_{n}^{k_{n}},则phi(N) = N \times (1 - \frac{1}{P_{1}}) \times (1 - \frac{1}{P_{2}}) \times \dots \times (1 - \frac{1}{P_{n}})
N=P1k1×P2k2×⋯×Pnkn,则phi(N)=N×(1−P11)×(1−P21)×⋯×(1−Pn1)
比如 6=2×3,则phi(6) = 6 * (1 - 1/2) * (1-1/3) = 2,即1~6中有两个与6互质的数;
欧拉函数的应用:
欧拉定理:
若
a
与
n
互质,那么有
a
ϕ
(
n
)
m
o
d
n
=
1
若a与n互质,那么有a^{\phi(n)} mod \ n=1
若a与n互质,那么有aϕ(n)mod n=1
费马定理:
若
a
与
p
互质,
p
是质数,那么有
a
ϕ
(
p
)
m
o
d
p
=
a
p
−
1
m
o
d
p
=
1
若a与p互质,p是质数,那么有a^{\phi(p)} mod\ p = a^{p-1} mod\ p = 1
若a与p互质,p是质数,那么有aϕ(p)mod p=ap−1mod p=1
定义求欧拉函数
Acwing 873.欧拉函数
实现思路:得到质因数,然后按公式相乘
注意:每次得到质因数i,应按照公式乘以(1-1/i),但为避免小数,先除再乘,即(1-1/i)=/i*(i-1)
,res初始化为x;
具体实现代码(详解版):
#include <iostream>
using namespace std;
// 计算单个数 x 的欧拉函数值 φ(x)
int phi(int x) {
int res = x; // 初始时,欧拉函数的值设置为 x
// 遍历所有可能的质因数 i,i 的范围从 2 到 sqrt(x)
for (int i = 2; i <= x / i; i++) {
if (x % i == 0) { // 如果 i 是 x 的质因数
res = res / i * (i - 1); // 使用公式 φ(x) = φ(x) / i * (i - 1)
while (x % i == 0) x /= i; // 不断除去 i,直到 x 不再能被 i 整除
}
}
// 如果最后剩余的 x > 1,说明它本身是一个大于 sqrt(x) 的质数
if (x > 1) res = res / x * (x - 1); // 使用公式 φ(x) = φ(x) / x * (x - 1)
return res;
}
int main() {
int n;
cin >> n;
while (n--) {
int x;
cin >> x;
cout << phi(x) << endl; // 输出 x 的欧拉函数值
}
return 0;
}
2.筛法求欧拉函数
利用质数的线性筛法求1-n的欧拉函数
由于在线性筛法的执行过程中,对于质数会保留,对于合数会用其最小质因子筛掉。所以线性筛法是会访问到所有数的。而根据上面的推导,在遇到每种情况时,我们都能求出欧拉函数
-
当i是质数:
phi(i)=i-1
,因为i为质数,那么前i-1个都与其互质 -
当i是合数:
某个合数一定是被
pj * i
给筛掉的,我们就在筛他的时候求他的欧拉函数值- 如果
i % pj == 0
,那么pj
就是i
的某个质因数,那么pj*i
和i
的质因数组合完全相同,根据欧拉函数公式,所以phi(pj *i) = pj * phi(i)
- 如果
i % pj != 0
,pj不是i的质因数,那么pj*i
的质因数组合就是在i
的质因数组合基础上加了一个质数pj
,根据欧拉函数公式,所以phi(pj * i) = pj * phi(i) *(1 - 1/pj) = (pj - 1) * phi(i)
- 如果
Acwing 874.筛法求欧拉函数
具体实现代码(详解版):
#include <iostream>
using namespace std;
typedef long long LL;
const int N = 1000010; // 最大范围 N
int primes[N], cnt; // primes[] 用于存储筛出的素数,cnt 记录素数的个数
int phi[N]; // phi[] 用于存储每个数的欧拉函数值
bool st[N]; // st[] 标记数组,用于标记某个数是否已经被筛掉(是否是合数)
// 计算 1 到 n 之间所有数的欧拉函数之和
LL get_eulers(int n) {
phi[1] = 1; // 欧拉函数的初始值,φ(1) = 1
// 线性筛法,计算欧拉函数
for (int i = 2; i <= n; i++) {
if (!st[i]) { // 如果 i 还没有被标记,说明 i 是素数
primes[cnt++] = i; // 将 i 加入素数数组
phi[i] = i - 1; // 对于素数 i,有 φ(i) = i - 1
}
// 枚举每个素数 primes[j],并标记 primes[j] * i 为合数
for (int j = 0; primes[j] <= n / i; j++) {
st[primes[j] * i] = true; // 标记 primes[j] * i 为合数
if (i % primes[j] == 0) { // 如果 i 能被 primes[j] 整除
// 说明 primes[j] 是 i 的最小质因数,直接使用公式 φ(primes[j] * i) = φ(i) * primes[j]
phi[primes[j] * i] = phi[i] * primes[j];
break; // 因为 primes[j] 是 i 的最小质因数,故无需继续枚举
}
// 如果 i 不能被 primes[j] 整除,使用公式 φ(primes[j] * i) = φ(i) * (primes[j] - 1)
phi[primes[j] * i] = phi[i] * (primes[j] - 1);
}
}
// 累加 1 到 n 的所有欧拉函数值
LL res = 0;
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;
}
总结:
- 方法一(直接计算):适合当你只需要计算单个或少量数字的欧拉函数时,使用这种方法简单有效。
- 方法二(线性筛法):适合处理大量数字的欧拉函数计算,如 1 到 𝑛 的所有数字,或者需要频繁查询时,先预处理一次后可以快速查找。