竞赛小算法总结(二):gcdlcm,拓展欧几里得线性同余,逆元(含代码详解)
一 gcd&lcm
这个其实也属于是很简单但非常有必要掌握的小算法。
1. 定义
“gcd” 是 “Greatest Common Divisor” 的缩写,中文名为最大公约数 ,也称最大公因数。它指的是两个或多个整数共有约数中最大的一个。例如,对于整数 12 和 18 ,12 的约数有 (1, 2, 3, 4, 6, 12) ,18 的约数有 (1, 2, 3, 6, 9, 18) ,它们共有的约数有 (1, 2, 3, 6) ,其中最大的是 6 ,所以 12 和 18 的最大公约数(gcd )是 6 ,可表示为 (gcd(12, 18)=6) 。
2. 计算方法
这个其实也不用完全死记,大概记住现场也可以推一下。
下面是代码
/*** 使用辗转相除法计算两个整数的最大公约数(GCD)。* 原理:对于任意两个整数a和b,gcd(a, b) = gcd(b, a mod b),* 不断重复此过程直到余数为0,此时的除数即为最大公约数。* * @param a 第一个整数* @param b 第二个整数(不能为0,否则会导致除零异常)* @return a和b的最大公约数*/
public static int gcd(int a, int b) {// 计算a除以b的余数int k = a % b;// 当余数为0时,当前的除数b就是最大公约数if (k == 0) {return b;}// 否则递归计算b和余数k的最大公约数return gcd(b, k);
}
lcm就是最小公倍数,我们直接a*b/gcd(a,b)就可以了。
代码:
public static int lcm(int a,int b) {return a*b/gcd(a,b);}
二 拓展欧几里得&线性同余
拓展欧几里得算法(Extended Euclidean Algorithm)
基本定义 拓展欧几里得算法是欧几里得算法(辗转相除法)的扩展,不仅能计算两个整数 a 和 b 的最大公约数 (d = gcd(a, b)),还能找到整数 x 和 y,使得:ax + by = d其中,x 和 y 被称为 贝祖系数(Bézout coefficients)。
贝祖系数一般用于求解求解线性不定方程ax + by = d,题目可能设计成以这个为原理的故事算法题,还有就是第三部分要讲的求模逆元。
下面来看怎么求贝祖系数:
在计算gcd时进行回溯。
// 扩展欧几里得算法 - 递归实现public static long[] extendedGCD(long a, long b) {if (b == 0) {// 终止条件:gcd(a, 0) = a,对应的贝祖系数为 (1, 0)return new long[]{a, 1, 0};} else {// 递归计算 gcd(b, a mod b)long[] result = extendedGCD(b, a % b);//接受上一层的返回值long gcd = result[0];long x = result[2];long y = result[1] - (a / b) * result[2];return new long[]{gcd, x, y};}}
三 模拟元
逆元(模运算中的乘法逆元)
在模运算中,逆元(Multiplicative Inverse) 是一个核心概念,用于解决除法问题。对于整数 a 和模数 m,若存在整数 x 使得:
则称 x 为 a 在模 m 下的逆元。逆元存在的充要条件是 a 和 m 互质(即 gcd(a, m) = 1)。
1. 扩展欧几里得算法
当 a 和 m 互质时,通过扩展欧几里得算法求解 ax + my = 1,得到的 x 即为逆元。
// 计算模逆元:若 a 和 m 互质,返回 a 在模 m 下的逆元;否则返回 -1public static long modInverse(long a, long m) {long[] result = extendedGCD(a, m);long gcd = result[0];long x = result[1];if (gcd != 1) {// a 和 m 不互质,逆元不存在return -1;} else {// 确保逆元在 [0, m-1] 范围内return (x % m + m) % m;}}
2. 费马小定理(当 m 为质数时)
代码:
public static long powMod(long a, long b, long mod) {long result = 1; // 初始化结果为1while (b > 0) { // 循环直到指数b变为0if ((b & 1) != 0) { // 检查b的二进制最低位是否为1(等价于b % 2 != 0)result = result * a % mod; // 若为1,将当前底数a乘入结果并取模}a = a * a % mod; // 底数平方并取模b >>= 1; // 指数右移一位(等价于b /= 2)}return result;
}
讲实话,我碰到的最多的是gcd和lcm和逆元,逆元一般用于求含除数运算的数的模,如42!/2024%100000007。