1. 项目概述
作为一名长期从事算法竞赛培训的教练,我发现很多学生在CSP-S提高组竞赛中遇到数论题目时往往束手无策。特别是欧拉函数和欧拉定理这两个核心概念,虽然理论理解起来不难,但实际编程应用时却频频出错。今天我就来分享一套经过实战检验的教学方案,通过具体编程案例带你真正掌握这两个重要工具。
欧拉函数φ(n)在数论中表示小于n的正整数中与n互质的数的个数,而欧拉定理则建立了模运算下的幂次简化关系。这两个概念在密码学、算法优化等领域都有广泛应用,也是信奥赛中的高频考点。本专题将从实际编程角度出发,通过典型例题解析和代码实现,让你不仅理解理论,更能熟练应用于竞赛实战。
2. 欧拉函数的核心原理与实现
2.1 欧拉函数的数学定义
欧拉函数φ(n)的计算基于n的质因数分解。对于一个正整数n,若其质因数分解为n = p₁^k₁ * p₂^k₂ * ... * p_m^k_m,则欧拉函数的计算公式为:
φ(n) = n * (1 - 1/p₁) * (1 - 1/p₂) * ... * (1 - 1/p_m)
这个公式的直观理解是:从n个数字中去掉所有p₁的倍数、p₂的倍数等等,同时注意处理被多个质因数同时整除的情况(容斥原理)。
2.2 欧拉函数的编程实现
在实际编程中,我们通常采用两种方法计算欧拉函数:
- 直接计算法:基于质因数分解的实现
cpp复制int euler_phi(int n) {
int result = n;
for (int p = 2; p * p <= n; ++p) {
if (n % p == 0) {
while (n % p == 0)
n /= p;
result -= result / p;
}
}
if (n > 1)
result -= result / n;
return result;
}
- 筛法预处理:当需要多次查询时的高效方法
cpp复制const int MAX_N = 1e6;
int phi[MAX_N + 1];
void compute_phi() {
for (int i = 1; i <= MAX_N; i++)
phi[i] = i;
for (int p = 2; p <= MAX_N; p++) {
if (phi[p] == p) { // p is prime
for (int multiple = p; multiple <= MAX_N; multiple += p)
phi[multiple] -= phi[multiple] / p;
}
}
}
注意:筛法虽然预处理时间复杂度为O(n log log n),但之后每次查询都是O(1),适合需要大量查询的场景。
2.3 欧拉函数的性质与应用场景
欧拉函数有几个重要性质在编程中经常用到:
- 对于质数p,φ(p) = p-1
- 若m和n互质,则φ(mn) = φ(m)φ(n)
- 对于n > 2,φ(n)总是偶数
在竞赛中,欧拉函数的典型应用包括:
- 求解模运算下的逆元
- 优化指数运算(配合欧拉定理)
- 解决特定类型的计数问题
- RSA加密算法等密码学应用
3. 欧拉定理的深入解析
3.1 欧拉定理的数学表述
欧拉定理指出:若两个正整数a和n互质,则满足:
a^φ(n) ≡ 1 (mod n)
这是一个广义化的费马小定理(当n为质数时,φ(n)=n-1,退化为费马小定理)。
3.2 欧拉定理的编程应用
欧拉定理在编程中最常见的应用是简化大指数运算。例如计算a^b mod n时,如果a和n互质,我们可以利用欧拉定理将指数b缩小:
a^b ≡ a^(b mod φ(n)) (mod n)
这在处理大数运算时非常有用,可以避免直接计算超大指数。实现代码示例:
cpp复制// 假设已经实现了euler_phi函数
long long modular_exponentiation(long long a, long long b, long long n) {
if (__gcd(a, n) == 1) { // a和n互质
long long phi = euler_phi(n);
b = b % phi;
}
// 常规的快速幂计算
long long result = 1;
a = a % n;
while (b > 0) {
if (b % 2 == 1)
result = (result * a) % n;
a = (a * a) % n;
b = b / 2;
}
return result;
}
3.3 欧拉定理的扩展应用
在实际编程中,即使a和n不互质,我们也可以使用欧拉定理的扩展形式。当n ≥ 1且a和n有公因数时,存在以下关系:
a^b ≡ a^(b mod φ(n) + φ(n)) (mod n) 当b ≥ φ(n)时
这个扩展形式在竞赛题目中经常出现,需要特别注意。
4. 典型竞赛题目解析
4.1 例题1:求大数的模
题目描述:计算7^2023 mod 100的值。
解题思路:
- 首先计算φ(100)。100 = 2² × 5²,所以φ(100) = 100 × (1-1/2) × (1-1/5) = 40
- 因为7和100互质,根据欧拉定理,7^40 ≡ 1 mod 100
- 将指数分解:2023 = 40×50 + 23
- 因此7^2023 ≡ (7^40)^50 × 7^23 ≡ 1^50 × 7^23 ≡ 7^23 mod 100
- 现在只需要计算7^23 mod 100,可以通过快速幂实现
实现代码:
cpp复制#include <iostream>
using namespace std;
int fast_exp(int base, int exp, int mod) {
int result = 1;
while (exp > 0) {
if (exp % 2 == 1)
result = (result * base) % mod;
base = (base * base) % mod;
exp = exp / 2;
}
return result;
}
int main() {
int phi_100 = 40; // φ(100)=40
int exponent = 2023 % phi_100;
cout << fast_exp(7, exponent, 100) << endl;
return 0;
}
4.2 例题2:求解线性同余方程
题目描述:解方程 17x ≡ 9 (mod 26)
解题思路:
- 这需要找到17在模26下的乘法逆元
- 因为17和26互质,逆元存在且等于17^(φ(26)-1) mod 26
- 计算φ(26)=12(因为26=2×13)
- 所以逆元为17^11 mod 26
- 使用快速幂计算后得到逆元为23
- 因此x ≡ 9×23 mod 26 ≡ 207 mod 26 ≡ 25
实现代码:
cpp复制int inverse(int a, int n) {
int phi = euler_phi(n);
return fast_exp(a, phi - 1, n);
}
int solve_equation() {
int a = 17, b = 9, n = 26;
int inv = inverse(a, n);
return (b * inv) % n;
}
5. 实战技巧与常见错误
5.1 性能优化技巧
- 预处理欧拉函数值:在需要多次查询欧拉函数的题目中,使用筛法预处理φ值数组
- 记忆化计算结果:对于递归计算的题目,使用unordered_map存储中间结果
- 快速幂的位运算优化:用位运算代替除法和取模
cpp复制while (exp > 0) {
if (exp & 1)
result = (result * base) % mod;
base = (base * base) % mod;
exp >>= 1;
}
5.2 常见错误与调试
- 忽略互质条件:应用欧拉定理前必须确认a和n互质
- 边界条件处理:n=1时φ(1)=1,但所有数都与1不互质
- 整数溢出问题:在计算过程中及时取模,特别是乘法运算
- 错误理解扩展欧拉定理:记住只有当b≥φ(n)时才适用扩展形式
5.3 测试用例设计
设计测试用例时应考虑:
- 小质数情况(如n=7)
- 大合数情况(如n=1000)
- 极端情况(n=1)
- a和n不互质的情况
- 大指数情况(如b=1e18)
示例测试用例:
cpp复制assert(euler_phi(1) == 1);
assert(euler_phi(7) == 6);
assert(euler_phi(100) == 40);
assert(modular_exponentiation(7, 2023, 100) == 43);
assert(solve_equation(17, 9, 26) == 25);
6. 综合应用案例
6.1 RSA加密算法模拟
RSA算法的核心正是基于欧拉定理。下面我们实现一个简化的RSA加密过程:
cpp复制#include <iostream>
#include <tuple>
using namespace std;
tuple<int, int, int> generate_keys(int p, int q) {
int n = p * q;
int phi = (p - 1) * (q - 1);
// 选择公钥e (通常取65537,这里简化为小值)
int e = 5;
while (__gcd(e, phi) != 1)
e++;
// 计算私钥d (e的模逆元)
int d = fast_exp(e, phi - 1, phi);
return make_tuple(e, d, n);
}
int encrypt(int message, int e, int n) {
return fast_exp(message, e, n);
}
int decrypt(int cipher, int d, int n) {
return fast_exp(cipher, d, n);
}
int main() {
int p = 61, q = 53; // 两个质数
auto [e, d, n] = generate_keys(p, q);
int message = 1234;
int cipher = encrypt(message, e, n);
int decrypted = decrypt(cipher, d, n);
cout << "Original: " << message << endl;
cout << "Encrypted: " << cipher << endl;
cout << "Decrypted: " << decrypted << endl;
return 0;
}
6.2 竞赛题目实战
题目:计算1^k + 2^k + ... + n^k mod m (n≤1e12, k≤1e6)
优化思路:
- 当a和m互质时,a^k mod m = a^(k mod φ(m)) mod m
- 当不互质时,使用扩展欧拉定理
- 预处理φ(m)的值
- 对每个a∈[1,n],分类讨论后使用快速幂
核心代码片段:
cpp复制long long solve(long long n, long long k, int m) {
long long sum = 0;
int phi = euler_phi(m);
for (long long a = 1; a <= n; a++) {
if (__gcd(a, m) == 1) {
long long exp = k % phi;
sum = (sum + fast_exp(a, exp, m)) % m;
} else {
if (k >= phi) {
long long exp = k % phi + phi;
sum = (sum + fast_exp(a, exp, m)) % m;
} else {
sum = (sum + fast_exp(a, k, m)) % m;
}
}
}
return sum;
}
在实际教学中,我发现学生最容易犯的错误是忽略a和m的互质条件,或者错误应用扩展欧拉定理。通过这个系统的专题训练,配合典型例题的反复练习,学生通常能在2-3周内熟练掌握欧拉函数和欧拉定理的编程应用。