
在算法竞赛的数论领域,欧拉函数是连接 “互质” 与 “实际问题” 的关键桥梁。它看似抽象 —— 统计 1 到 n 中与 n 互质的数的个数,但背后藏着巧妙的数学逻辑和高效的计算方法。无论是仪仗队视线问题、GCD 计数问题,还是后续的欧拉降幂、同余方程求解,欧拉函数都扮演着核心角色。本文将从互质概念切入,层层拆解欧拉函数的定义、性质、计算方法,结合洛谷经典例题,手把手教你掌握从单个函数计算到批量打表的全流程,让你在数论题目中轻松举一反三。下面就让我们正式开始吧!
在理解欧拉函数之前,我们先明确 “互质” 的概念:若两个整数 a 和 b 的最大公约数为 1(即 gcd (a,b)=1),则称 a 和 b 互质(也称互素)。例如,5 和 8 互质(gcd (5,8)=1),而 6 和 9 不互质(gcd (6,9)=3)。
特别注意:
欧拉函数 φ(n)(读作 “phi (n)”)表示:在整数 1 到 n 的范围内,与 n 互质的数的个数。
举几个直观例子:
通过这些例子可以发现:质数 p 的欧拉函数 φ(p)=p-1(因为所有小于 p 的数都与 p 互质),这是欧拉函数的一个重要特殊情况。
根据算术基本定理,任何大于 1 的自然数 n 都可以唯一分解为质因数的乘积:n=p1α1×p2α2×⋯×pkαk其中 p₁<p₂<…<p_k 是质数,α₁、α₂、…、α_k 是正整数。
欧拉函数的数学表达式为:φ(n)=n×(1−p11)×(1−p21)×⋯×(1−pk1)
这个公式的核心思想是 “容斥原理”:从 n 个数字中,依次排除能被每个质因子 p_i 整除的数,最终剩下的就是与 n 互质的数。
举个例子,计算 φ(12):
欧拉函数的性质是解决复杂问题的核心,掌握这些性质能让你在解题时事半功倍。
这是最基础的性质,前面已经举例说明。因为质数 p 的约数只有 1 和 p,所以 1 到 p 中与 p 互质的数是所有小于 p 的正整数,共 p-1 个。
推导过程:
若 a 和 b 互质(gcd (a,b)=1),则 φ(a×b)=φ(a)×φ(b)。
这是欧拉函数最重要的性质,也是批量计算欧拉函数的核心依据。
例子:
推导:因为 n 是奇数,所以 2 和 n 互质,根据积性函数性质:φ(2n)=φ(2)×φ(n)=1×φ(n)=φ(n)。
例子:φ(6)=φ(2×3)=φ(3)=2(3 是奇数),与实际结果一致。
这是欧拉函数的一个重要恒等式,表示 n 等于其所有正约数的欧拉函数之和。
例子:n=6 的正约数是 1、2、3、6,Σφ(d)=φ(1)+φ(2)+φ(3)+φ(6)=1+1+2+2=6,与 n 相等。
这个性质在后续的数论问题中(如 GCD 计数)会经常用到,需要重点记忆。
根据欧拉函数的数学表达式,计算单个数字 n 的 φ(n) 可以按照以下步骤:
#include <iostream>
using namespace std;
typedef long long LL;
// 试除法计算单个数字的欧拉函数
LL phi(LL n) {
LL res = n; // 初始值为n
// 枚举质因子,直到sqrt(n)
for (LL i = 2; i <= n / i; ++i) {
if (n % i == 0) { // i是n的质因子
// 应用公式:res = res * (i-1) / i
res = res / i * (i - 1);
// 除尽所有i的倍数,避免重复计算
while (n % i == 0) {
n /= i;
}
}
}
// 若剩余n>1,说明是最后一个质因子
if (n > 1) {
res = res / n * (n - 1);
}
return res;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
LL n;
while (cin >> n) {
cout << "φ(" << n << ") = " << phi(n) << endl;
}
return 0;
}if(n>1)分支,返回 res = n * (n-1)/n = n-1,符合性质 1。当需要计算 1 到 n 范围内所有数字的欧拉函数时,试除法的时间复杂度为 O (n√n),对于 n=1e6 等大规模数据会超时。此时需要更高效的方法 —— 结合欧拉筛(线性筛),在 O (n) 时间内批量计算欧拉函数。
欧拉筛的核心是 “让每个合数只被其最小质因子筛掉”,结合欧拉函数的性质,我们可以在筛质数的同时递推计算每个数的 φ 值:
#include <iostream>
#include <vector>
using namespace std;
const int MAXN = 1e6 + 10;
bool st[MAXN]; // 标记是否为合数
int primes[MAXN], cnt; // 存储质数,cnt为质数个数
int phi[MAXN]; // 存储欧拉函数值
// 欧拉筛批量计算1~n的欧拉函数
void get_phi(int n) {
memset(st, false, sizeof st);
memset(phi, 0, sizeof phi);
cnt = 0;
phi[1] = 1; // 特殊情况:φ(1)=1
for (int i = 2; i <= n; ++i) {
if (!st[i]) { // i是质数
primes[++cnt] = i;
phi[i] = i - 1; // 性质1:质数的欧拉函数为i-1
}
// 枚举所有已找到的质数,筛掉i*primes[j]
for (int j = 1; 1LL * i * primes[j] <= n; ++j) {
int x = i * primes[j];
st[x] = true; // 标记为合数
if (i % primes[j] == 0) { // primes[j]是i的最小质因子,且是i的质因子
phi[x] = phi[i] * primes[j];
break;
} else { // primes[j]是i的最小质因子,但不是i的质因子(i与primes[j]互质)
phi[x] = phi[i] * (primes[j] - 1);
}
}
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n = 1e6;
get_phi(n);
// 输出1~10的欧拉函数值
for (int i = 1; i <= 10; ++i) {
cout << "φ(" << i << ") = " << phi[i] << endl;
}
return 0;
}批量计算欧拉函数适用于以下场景:
题目链接:https://www.luogu.com.cn/problem/P2158

题目描述:仪仗队是由 N×N 的学生组成的方阵,C 君站在方阵的左后方(坐标 (0,0)),视线沿直线延伸。当且仅当学生站在坐标 (i,j)(1≤i,j≤N)且 i 和 j 互质时,C 君才能看到该学生。求 C 君能看到的学生总数。
输入:一行一个正整数 N(N≤40000)。
输出:一行一个整数,表示能看到的学生人数。示例:输入 4 → 输出 9。
核心思路:
#include <iostream>
using namespace std;
typedef long long LL;
const int MAXN = 40010;
bool st[MAXN];
int primes[MAXN], cnt;
int phi[MAXN];
// 欧拉筛批量计算欧拉函数
void get_phi(int n) {
memset(st, false, sizeof st);
memset(phi, 0, sizeof phi);
cnt = 0;
phi[1] = 1;
for (int i = 2; i <= n; ++i) {
if (!st[i]) {
primes[++cnt] = i;
phi[i] = i - 1;
}
for (int j = 1; 1LL * i * primes[j] <= n; ++j) {
int x = i * primes[j];
st[x] = true;
if (i % primes[j] == 0) {
phi[x] = phi[i] * primes[j];
break;
} else {
phi[x] = phi[i] * (primes[j] - 1);
}
}
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int N;
cin >> N;
if (N == 1) { // 特殊情况:1×1方阵,只能看到自己(但题目中坐标是(1,1)?根据示例,N=1时输出0)
cout << 0 << endl;
return 0;
}
get_phi(N - 1); // 计算1~N-1的欧拉函数
LL sum = 0;
for (int i = 1; i < N; ++i) {
sum += phi[i];
}
cout << sum * 2 + 1 << endl; // 对称×2 + 对角线(1,1)
return 0;
}以 N=4 为例:
题目链接:https://www.luogu.com.cn/problem/P2568

题目描述:给定正整数 n,求 1≤x,y≤n 且 gcd (x,y) 为质数的数对 (x,y) 的个数。
输入:一行一个正整数 n(n≤1e7)。
输出:一行一个整数,表示满足条件的数对个数。示例:输入 4 → 输出 4。
核心思路:
#include <iostream>
using namespace std;
typedef long long LL;
const int MAXN = 1e7 + 10;
bool st[MAXN];
int primes[MAXN], cnt;
int phi[MAXN];
LL f[MAXN]; // f[k] = φ(1) + φ(2) + ... + φ(k)
// 欧拉筛批量计算欧拉函数和前缀和
void get_phi_and_prefix(int n) {
memset(st, false, sizeof st);
memset(phi, 0, sizeof phi);
cnt = 0;
phi[1] = 1;
for (int i = 2; i <= n; ++i) {
if (!st[i]) {
primes[++cnt] = i;
phi[i] = i - 1;
}
for (int j = 1; 1LL * i * primes[j] <= n; ++j) {
int x = i * primes[j];
st[x] = true;
if (i % primes[j] == 0) {
phi[x] = phi[i] * primes[j];
break;
} else {
phi[x] = phi[i] * (primes[j] - 1);
}
}
}
// 计算前缀和f[k]
f[0] = 0;
for (int i = 1; i <= n; ++i) {
f[i] = f[i - 1] + phi[i];
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
get_phi_and_prefix(n);
LL ans = 0;
// 遍历所有质数d,计算贡献
for (int i = 1; i <= cnt; ++i) {
int d = primes[i];
int k = n / d;
ans += 2 * f[k] - 1;
}
cout << ans << endl;
return 0;
}以 n=4 为例:
欧拉函数是欧拉降幂的核心,用于处理大指数幂取模问题。扩展欧拉定理指出:对于任意整数 a、m 和正整数 b,有:

应用场景:当 b 极大时(如 b=1e18),直接计算 a^b mod m 是不可能的,此时可以用欧拉降幂将指数缩小。
在解同余方程 ax≡1 (mod m) 时,若 a 和 m 互质,则方程的解为 a 的乘法逆元,而根据欧拉定理,a^(φ(m)-1) mod m 就是 a 的一个逆元(因为 a^φ(m)≡1 (mod m),两边除以 a 得 a^(φ(m)-1)≡a^(-1)(mod m))。
除了本文介绍的 GCD 计数、仪仗队问题,欧拉函数还常用于以下计数场景:
欧拉函数是数论中极具实用性的函数,其核心是 “统计互质数的个数”,但通过性质推导和算法优化,能解决一系列复杂的竞赛问题。 在实际竞赛中,欧拉函数往往不是孤立出现的,而是与质数、GCD、同余方程、降幂等知识点结合考查。因此,除了掌握本文的内容,还需要多做综合性题目,加深对欧拉函数性质的理解,提升知识的融会贯通能力。 如果你在学习过程中遇到具体题目无法解决,或者想进一步了解欧拉降幂、同余方程等延伸知识点,可以随时留言交流。后续将持续更新数论进阶内容,敬请关注!