欧拉函数与快速幂
欧拉函数
欧拉函数 φ(n) 是「小于等于 n 且与 n 互质的正整数个数」,记作
φ(n) = |{ 1 ≤ k ≤ n : gcd(k, n) = 1 }|
它是一切「模运算、乘法群、RSA 密码」的基石
n = p₁^e₁ · p₂^e₂ … p_k^e_k
φ(n) = ∏ φ(pi^ei) = ∏ pi^ei(1−1/pi) = n · ∏(1−1/pi)
欧拉函数 φ(n) 是「小于等于 n 且与 n 互质的正整数个数」,记作
φ(n) = |{ 1 ≤ k ≤ n : gcd(k, n) = 1 }|它同时是一个积性函数,也是 RSA、模逆、欧拉定理的基石。
一、定义与例子 n | 1 2 3 4 5 6 7 8 9 10 φ(n)|1 1 2 2 4 2 6 4 6 4 验证 φ(6)=2:{1,5} 与 6 互质。
二、通用公式 若 n = p₁^e₁ · p₂^e₂ … pk^ek (标准质因分解) 则 φ(n) = n · ∏{i=1..k} (1 − 1/pi) = ∏{i=1..k} (pi^ei − pi^{ei−1})
三、常用特例
- p 为素数 ⇒ φ(p) = p−1
- φ(p^e) = p^{e−1}(p−1)
- gcd(m,n)=1 ⇒ φ(mn)=φ(m)φ(n) (积性)
四、核心性质
- 约数和:∑_{d|n} φ(d) = n
- 欧拉定理:gcd(a,n)=1 ⇒ a^{φ(n)} ≡ 1 (mod n)
- 费马小定理:n 素数 ⇒ a^{n−1} ≡ 1 (mod n)
五、代码模板
- 单点 O(√n)
static long phi(long n) {long res = n;for (long p = 2; p * p <= n; p++)if (n % p == 0) {res = res / p * (p - 1);while (n % p == 0) n /= p;}if (n > 1) res = res / n * (n - 1);return res;}
- 线性筛批量 φ[1..n] O(n)
static int[] phiTable(int n) {int[] phi = new int[n + 1];for (int i = 0; i <= n; i++) phi[i] = i;for (int i = 2; i <= n; i++)if (phi[i] == i) // i 是质数for (int j = i; j <= n; j += i)phi[j] = phi[j] / i * (i - 1);return phi;}
六、秒记口诀 「先质因分解,再乘 (1−1/p);
筛法遇素数,phi[j]=phi[j]/p*(p-1)」记住这张图,φ(n) 永远满分!
单点求 φ(n) 的实现要点: 初始 res = n;每找到一个质因子 p,就执行 res = res / p * (p - 1)(即乘上 (1 - 1/p)),并把 p 从 n 中除尽;最后剩下的 n > 1 是最后一个质因子,同样处理。注意”先除后乘”避免溢出。
import java.io.*;import java.util.StringTokenizer;
public class Main { /* 求单个数的欧拉函数 phi(a) */ static int phi(int a) { int res = a; for (int i = 2; i <= a / i; i++) if (a % i == 0) { // 找到质因子 i res = res / i * (i - 1); while (a % i == 0) a /= i; } if (a > 1) res = res / a * (a - 1); return res; }
public static void main(String[] args) throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); int n = Integer.parseInt(br.readLine().trim()); StringBuilder out = new StringBuilder(); while (n-- > 0) { int a = Integer.parseInt(br.readLine().trim()); out.append(phi(a)).append('\n'); } System.out.print(out); }}线性筛法求欧拉函数
import java.io.*;
public class Main { static final int N = 1_000_010; static int[] primes = new int[N], phi = new int[N]; static boolean[] st = new boolean[N]; static int cnt;
static long getEulers(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; j < cnt && primes[j] <= n / i; j++) { st[primes[j] * i] = true; if (i % primes[j] == 0) { phi[primes[j] * i] = phi[i] * primes[j]; break; } else { phi[primes[j] * i] = phi[i] * (primes[j] - 1); } } } long res = 0; for (int i = 1; i <= n; i++) res += phi[i]; return res; }
public static void main(String[] args) throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); int n = Integer.parseInt(br.readLine().trim()); System.out.println(getEulers(n)); }}欧拉定理
欧拉定理(Euler’s Theorem)是数论中最重要的“幂次模”工具之一,一句话先记住:
若整数 a 与正整数 n 互质,则 a^{φ(n)} ≡ 1 (mod n) 其中 φ(n) 是欧拉函数。
一、标准表述 条件:gcd(a, n) = 1 结论:a^{φ(n)} ≡ 1 (mod n)
二、例子验证 n = 10,φ(10) = 4,取 a = 3(gcd(3,10)=1) 3^4 = 81 ≡ 1 (mod 10) ✔
n = 7,φ(7) = 6,任意 a∈{1,2,3,4,5,6} a^6 ≡ 1 (mod 7) ✔(这就是费马小定理)
三、直观理解 把「与 n 互质」的所有剩余看成乘法群 (ℤ/nℤ)^×,它的阶(元素个数)正是 φ(n)。 群论里 Lagrange 定理 ⇒ 任意元素的阶整除群的阶,于是 a^{φ(n)} ≡ 1。
四、常见应用
- 模逆元:gcd(a,n)=1 ⇒ a^{-1} ≡ a^{φ(n)-1} (mod n)
- 降幂:a^b mod n 可把指数 b 模 φ(n)(再补 φ(n) 防 0)
- RSA 加密:解密指数 d 选成 e^{-1} mod φ(n)
欧拉定理的证明
若整数 与正整数 互质,则
一、符号与条件
- :与 互质且 的正整数个数(欧拉函数)
二、证明(乘积法 / 排列法)
-
取出与 互质的全部剩余
-
乘以 再模 得到新集合
-
与 的关系
- 互质性: 每个 仍
- 互异性:若 ,则
因 ,可消去
故 是 的一个排列。
-
两边同时连乘
-
提取公因子
-
消去 (因 与 互质,可逆)
快速幂
核心思想:二进制拆分指数。 求 a^k mod p,把指数 k 按二进制拆开:k = 2^0·b0 + 2^1·b1 + …,于是 a^k = a^{b0} · (a²)^{b1} · (a⁴)^{b2} · …。算法维护底数 a 不断自乘(a = a*a % p 得到 a², a⁴, a⁸…),当 k 的当前二进制位为 1 时,把当前底数乘进答案。这样只需要 O(log k) 次乘法,而不是朴素做法的 k 次。
题意
给定 组 ,对于每组数据求
输入格式
第一行一个整数 。
接下来 行,每行三个整数 。输出格式
对于每组数据,输出一行结果 。
数据范围
import java.io.*;import java.util.StringTokenizer;
public class Main { /* 快速幂:a^k mod p */ static int qmi(int a, int k, int p) { int res = 1 % p; // 应对 p=1 的情况 while (k > 0) { if ((k & 1) == 1) res = (int) ((long) res * a % p); a = (int) ((long) a * a % p); k >>= 1; } return res; }
public static void main(String[] args) throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); PrintWriter pw = new PrintWriter(new OutputStreamWriter(System.out)); int n = Integer.parseInt(br.readLine().trim()); while (n-- > 0) { StringTokenizer st = new StringTokenizer(br.readLine()); int a = Integer.parseInt(st.nextToken()); int k = Integer.parseInt(st.nextToken()); int p = Integer.parseInt(st.nextToken()); pw.println(qmi(a, k, p)); } pw.flush(); }}快速幂求逆元
乘法逆元(Modular Multiplicative Inverse)
题意
给定 组 ,其中 为质数,求
即满足
的最小正整数 。若逆元不存在,输出impossible。输入格式
第一行一个整数 。
接下来 行,每行两个整数 ,保证 为质数。输出格式
对于每组数据输出一行结果:
- 若 ,输出逆元 ;
- 否则输出
impossible。数据范围
关键公式(费马小定理)
当 为质数且 时,
为什么 a^{p-2} 是逆元? 费马小定理:p 为质数且 a 不被 p 整除时,a^{p-1} ≡ 1 (mod p)。两边同乘 a^{-1} 得 a^{p-2} ≡ a^{-1} (mod p)。所以”求逆元”转化为一次快速幂。注意:若 a % p == 0(即 a 是 p 的倍数),gcd(a,p) ≠ 1,逆元不存在。
import java.io.*;import java.util.StringTokenizer;
public class Main { /* 快速幂:a^k mod p */ static int qmi(int a, int k, int p) { int res = 1 % p; // 应对 p=1 的情况 while (k > 0) { if ((k & 1) == 1) res = (int) ((long) res * a % p); a = (int) ((long) a * a % p); k >>= 1; } return res; }
public static void main(String[] args) throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); PrintWriter pw = new PrintWriter(new OutputStreamWriter(System.out)); int n = Integer.parseInt(br.readLine().trim()); while (n-- > 0) { StringTokenizer st = new StringTokenizer(br.readLine()); int a = Integer.parseInt(st.nextToken()); int p = Integer.parseInt(st.nextToken()); int res = qmi(a, p-2, p);// 费马小定理:逆元 = a^(p-2) mod p if(a % p != 0){// 等价 gcd(a, p) = 1 pw.println(res); }else{ pw.println("impossible"); }
} pw.flush(); }}扩展欧几里得算法
解决的问题: 求不定方程 ax + by = gcd(a, b) 的一组整数解 (x, y)。
推导思路: 递归到边界 b = 0 时,方程变为 a·1 + 0·0 = a,取 x = 1, y = 0。假设下层递归已经解出 bx’ + (a mod b)y’ = g,把 a mod b = a - ⌊a/b⌋·b 代入整理,可得 g = a·y’ + b·(x’ - ⌊a/b⌋·y’),因此上层解为 x = y’,y = x’ - (a/b)·y’。这就是代码中 x、y 回代两行的来历。
应用: 求逆元(当 gcd(a, m) = 1 时解 ax ≡ 1 (mod m))、解线性同余方程、中国剩余定理的合并步骤。
题意
给定 对正整数 ,对于每对数,求出一组 使其满足
输入格式
第一行一个整数 。
接下来 行,每行两个正整数 。输出格式
共 行,每行输出两个整数 (任意合法解均可)。
数据范围
import java.io.*;import java.util.StringTokenizer;
public class Main { /* 扩展欧几里得:求 ax + by = gcd(a,b) 的一组解 (x,y),返回 gcd */ static long exgcd(long a, long b, long[] xy) { if (b == 0) { xy[0] = 1; xy[1] = 0; return a; } long g = exgcd(b, a % b, xy); long x = xy[0], y = xy[1]; xy[0] = y; xy[1] = x - (a / b) * y; return g; }
public static void main(String[] args) throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); PrintWriter pw = new PrintWriter(new OutputStreamWriter(System.out)); int n = Integer.parseInt(br.readLine().trim()); while (n-- > 0) { StringTokenizer st = new StringTokenizer(br.readLine()); long a = Long.parseLong(st.nextToken()); long b = Long.parseLong(st.nextToken()); long[] xy = new long[2]; exgcd(a, b, xy); pw.println(xy[0] + " " + xy[1]); } pw.flush(); }}如果这篇文章对你有帮助,欢迎分享给更多人!
部分信息可能已经过时