乘法逆元
数学基础
在模运算中,加减乘法都可以简单地进行转换,但是除法却不能这么做。例如
在普通的计算中,除以一个数等于乘上这个数的倒数(即逆元),即
不同的 $p$ 下,$b^{-1}$ 的值不一定相同!
至此,如何在除法中引入模运算的问题被转化为了求取逆元的问题,而这一问题有非常多种解法,以下围绕公式
费马小定理
费马小定理指出,当
两边同时乘上
费马小定理要求 $p$ 为素数,且 $a$ 和 $p$ 互素!
欧拉定理
欧拉函数
记
- 当
时,
给出欧拉定理,当
欧拉定理证明
- 取
的缩系 ,则 也是 的缩系 - 构造等式
- 等式两边同时除去
,即可得
欧拉定理要求 $a$ 和 $p$ 互素。
费马小定理是欧拉定理在 $p$ 为素数时的特殊情况。
扩展欧几里得
根据裴蜀定理:若
推导拓展欧几里
- 假设
- 显然当
时- 此时
- 当
时- 设
- 根据朴素的欧几里得原理有
- 则
- 整理得
- 可得
- 设
- 综上,可得
的递推式并求解
公式
拓展欧几里得要求 $a$ 和 $p$ 互素!
线性递推
若
记
递推式证明
- 令
,其中 - 有
- 因为
,有 - 代入
得 - 所以
- 因为
且 ,所以在知晓前 项时,可以在常数时间内计算出第 项,递推式成立
线性递推要求 $p$ 为素数,且 $a$ 和 $p$ 互素!
代码
幂运算的结果可能很大,应当注意数据类型以避免溢出!
费马小定理
export function modInverseFermat(a: bigint, p: bigint): bigint {
return quickMod(a, p - 2n, p);
}
2
3
def mod_inverse_fermat(a: int, p: int) -> int:
return quick_mod(a, p - 2, p)
2
/**
* number类型支持的最大安全整数仅有2^53-1,大约为9e15
* 在幂运算中非常容易溢出,建议使用更安全的bigint
*/
export function quickMod(x: bigint, p: bigint, mod: bigint): bigint {
let res: bigint = 1n;
while (p) {
if (p & 1n) res = (res * x) % mod;
x = (x * x) % mod;
p >>= 1n;
}
return res;
}
2
3
4
5
6
7
8
9
10
11
12
13
def quick_mod(x: int, p: int, mod: int) -> int:
res: int = 1
while p:
if p & 1:
res = (res * x) % mod
x = (x * x) % mod
p >>= 1
return res
2
3
4
5
6
7
8
欧拉定理
export function eulerPhi(n: bigint) {
let res: bigint = n;
for (let i = 2n; i * i <= n; i++) {
if (n % i === 0n) {
res = (res / i) * (i - 1n);
while (n % i === 0n) n /= i;
}
}
if (n > 1n) res = (res / n) * (n - 1n);
return res;
}
2
3
4
5
6
7
8
9
10
11
def euler_phi(n: int) -> int:
res = n
for i in range(2, math.floor(math.sqrt(n)) + 1):
if n % i == 0:
res = (res // i) * (i - 1)
while n % i == 0:
n //= i
if res > 1:
res = (res // n) * (n - 1)
return res
2
3
4
5
6
7
8
9
10
拓展欧几里得
export function modInverseExgcd(a: number, p: number): number {
const [gcd, x, y] = exgcd(a, p);
if (gcd !== 1) return -1; // a和p不互质,无法求出逆元
return (x + p) % p; // x可能为负数,需要加上p确保为正数
}
2
3
4
5
def mod_inverse_exgcd(a: int, p: int) -> int:
gcd, x, y = exgcd(a, p)
if gcd != 1:
return -1 # a和p不互质,无法求出逆元
return (x + p) % p # x可能为负数,需要加上p确保为正数
2
3
4
5
export function exgcd(a: number, b: number): [number, number, number] {
if (!b) return [a, 1, 0];
const [gcd, x, y] = exgcd(b, a % b);
return [gcd, y, x - Math.floor(a / b) * y];
}
2
3
4
5
def exgcd(a: int, b: int) -> int:
if b == 0:
return a, 1, 0
gcd, x, y = exgcd(b, a % b)
return gcd, y, x - a // b * y
2
3
4
5
线性递推
export function inverseLinear(p: number): number[] {
const inv: number[] = new Array<number>(p).fill(0);
inv[1] = 1;
for (let i = 2; i <= p - 1; i++)
inv[i] = ((p - Math.floor(p / i)) * inv[p % i]) % p;
return inv;
}
const invCache: Map<number, number[]> = new Map();
export function inverseLazy(a: number, p: number): number {
if (!invCache.has(p)) invCache.set(p, [0, 1]); // 初始化
const array = invCache.get(p);
if (array[a] === void 0)
array[a] = ((p - Math.floor(p / a)) * inverseLazy(p % a, p)) % p;
return array[a];
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def inverse_liner(p: int) -> List[int]:
inv: List[int] = []
inv[1] = 1
for i in range(2, p):
inv[i] = (p - p // i) * inv[p % i] % p
return inv
@lru_cache(maxsize=None)
def inverse_lazy(a: int, p: int) -> int:
if a == 1:
return 1
return (p - p // a) * inverse_lazy(p % a, p) % p
2
3
4
5
6
7
8
9
10
11
12
13