学习NOTE番外 1——浅谈逆元

想必 OIer 们一定对逆元很熟悉,但是总会有像本蒟蒻一样的 dalao 不太清楚,所以我来写写我的认知与感受。

我还是太菜了。

逆元是什么

定义有点难说,相当于是:逆元就是某个运算下的"撤销键"。在模运算里,逆元就是让 \(a\times x\) 变回 \(1\) 的那个 。

比如说一个数的倒数,也就是 \(a\times \frac{1}{a} (a\in \mathbb{R})\)之类的。

我们教练是这么说的:

若 \(a,b\) 互质,且满足线性同余方程:

\ax \\equiv 1 \\pmod{b} \\

则称 \(x\) 为 \(a\) 模 \(b\) 的乘法逆元,记作 \(a^{-1}\)。

也就是说,在模 \(b\) 的意义下,\(a\) 乘以 \(x\) 的结果和 1 同余 ,那么 \(x\) 就是 \(a\) 的逆元。

综上,讲义里的"逆元"就是模意义下的乘法逆元,核心公式是:

\a \\cdot a\^{-1} \\equiv 1 \\pmod{b} \\

前提是 \(\gcd(a,b)=1\)。


那为什么要互质呢?

因为方程

\ax \\equiv 1 \\pmod{b} \\

为什么是这个式子

我问了一下某只大鲸鱼。

大鲸鱼:

这是一个非常关键的问题,核心在于同余式 和不定方程之间的等价转化。

我们从定义出发,一步一步来看。


1. 同余式的定义

同余式

\ax \\equiv 1 \\pmod{b} \\

的意思是:\(ax\) 和 \(1\) 除以 \(b\) 的余数相同。

用整除的语言来说,就是:

\b \\mid (ax - 1) \\

也就是说,\(ax - 1\) 是 \(b\) 的整数倍。


2. 把"整除"写成等式

既然 \(b \mid (ax - 1)\),那么一定存在某个整数,使得

\ax - 1 = b \\cdot k \\

这里 \(k\) 是某个整数。

我们把这个等式整理一下:

\ax - bk = 1 \\

注意,\(k\) 只是一个整数,可以正也可以负。为了看起来更习惯,我们令

\y = -k \\

那么 \(y\) 也是整数,于是:

\ax + by = 1 \\

这就是:

等价于存在整数 \(y\),使得 \(ax + by = 1\)。


3. 反过来也成立

如果存在整数 \(x, y\) 使得

\ax + by = 1 \\

那么移项:

\ax - 1 = -by \\

右边是 \(b\) 的整数倍,所以

\b \\mid (ax - 1) \\

根据同余的定义,这正是:

\ax \\equiv 1 \\pmod{b} \\

所以两边是完全等价的。


4. 为什么这个转化很重要?

因为同余方程

\ax \\equiv 1 \\pmod{b} \\

不太好直接解,但转化成不定方程

\ax + by = 1 \\

之后,就可以用扩展欧几里得算法来求整数解了。

而扩展欧几里得算法恰好就是用来求

\ax + by = \\gcd(a,b) \\

的整数解的。当 \(\gcd(a,b)=1\) 时,右边就是 \(1\),正好就是我们要的形式。

所以整个逻辑链是:

\ax \\equiv 1 \\pmod{b} \\iff b \\mid (ax-1) \\iff \\exists y,\\ ax + by = 1 \\iff \\gcd(a,b)=1 \\

这也解释了为什么只有 \(a\) 和 \(b\) 互质时,\(a\) 模 \(b\) 才有乘法逆元。


总结

形式 含义
\(ax \equiv 1 \pmod{b}\) \(ax\) 和 \(1\) 模 \(b\) 同余
\(b \mid (ax-1)\) \(ax-1\) 是 \(b\) 的倍数
\(ax - 1 = bk\) 存在整数 \(k\)
\(ax + by = 1\) 令 \(y=-k\),得到不定方程

所以,同余方程和不定方程本质上是同一件事的两种写法。


当时我也没搞懂。
等价于存在整数 y,使得 ax + by = 1 根据裴蜀定理,这个方程有整数解当且仅当 \\gcd(a,b)=1

所以只有 \(a\) 和 \(b\) 互质 时,\(a\) 在模 \(b\) 下才有乘法逆元。不然你以为是啥。


逆元怎么求

目前有三种办法 (我学了的,我只学了这三种)。

法一:扩欧(扩展欧几里得算法)

先把

\ax \\equiv 1 \\pmod{b} \\

转化为不定方程:

\ax + by = 1 \\

然后用扩展欧几里得算法求出一组解 \((x_0, y_0)\)。

最后取

\(x_0 \\% b + b) \\% b \\

就是最小正整数逆元。

PID:P2613【模板】有理数取余


方法2:费马小定理求逆元

要求模数 \(b\) 是质数。

费马小定理说:

\a\^{p-1} \\equiv 1 \\pmod{p} \\

所以

\a \\cdot a\^{p-2} \\equiv 1 \\pmod{p} \\

因此

\a\^{-1} \\equiv a\^{p-2} \\pmod{p} \\

也就是说,当模数 \(p\) 是质数时,直接用快速幂算 \(a^{p-2} \bmod p\) 即可。

PID:P5431【模板】乘法逆元2


方法3:线性求逆元

问题:求 \(1,n\) 中每个数模质数 \(p\) 的逆元,且 \(p>n\)。

令

\p = kx + r \\

其中

\k = \\left\\lfloor \\frac{p}{x} \\right\\rfloor,\\quad r = p \\% x \\

可以推出:

\x\^{-1} \\equiv -k \\cdot r\^{-1} \\pmod{p} \\

代码里为了防止负数,写成:

\inv\[i = (p - p/i) \times invp\\%i \% p \]

给一下对应代码:

cpp 复制代码
inv[1]=1;
for(int i=2;i<=n;i++)
  inv[i]=(p-p/i)*inv[p%i]%p;

PID:P3811【模板】乘法逆元、P2155 SDOI2008 沙拉公主的困惑


举个例子

\8x \\equiv 1 \\pmod{5} \\

解得 \(x = 2, 7, 12, \dots\)

因为

\8 \\times 2 = 16 \\equiv 1 \\pmod{5} \\

所以 \(8\) 模 \(5\) 的逆元是 \(2\)。

逆元可以用来干什么

一、组合数取模(最高频)

厚着脸皮 安利一下我的组合数博客

核心公式

\C_n\^m \\equiv n! \\cdot (m!)\^{-1} \\cdot ((n-m)!)\^{-1} \\pmod{p} \\

预处理阶乘与阶乘逆元

cpp 复制代码
fact[0]=1;
for(int i=1;i<=n;i++)
    fact[i]=fact[i-1]*i%p;

inv[n]=fastpow(fact[n],p-2,p);//费马小定理
for(int i=n-1;i>=0;i---)
    inv[i]=inv[i+1]*(i+1)%p;

查询

cpp 复制代码
int C(int n, int m){
    if(m<0|| m > n) return 0;
    return (long long)fact[n] * inv_fact[m] % p * inv_fact[n-m] % p;
}
//此处我的代码里加了 #define int long long

适用条件

  • \(p\) 是质数
  • \(p > n\)(否则用 Lucas 定理)

二、解同余方程

形式

\ax \\equiv b \\pmod{p} \\

若 \(\gcd(a,p)=1\),则:

\x \\equiv b \\cdot a\^{-1} \\pmod{p} \\

求单个逆元

cpp 复制代码
// 扩展欧几里得
int exgcd(int a,int b,int &x,int &y){
    if(!b){
        x=1;
        y=0;
        return a;
    }
    int g=exgcd(b,a%b,y,x);
    y-=a/b*x;
    return g;
}
int inv(int a,int p){
    int x,y;
    exgcd(a,p,x,y);
    return(x%p+p)%p;
}
cpp 复制代码
// 费马小定理(p 为质数)
int inv(int a,int p){
    return fastpow(a,p-2,p);
}

三、BSGS 与扩展 BSGS

BSGS

求 \(a^x \equiv b \pmod{p}\),其中 \(\gcd(a,p)=1\)。

令 \(x = im - j\),其中 \(m = \lceil \sqrt{p} \rceil\),则:

\a\^{im} \\equiv b a\^j \\pmod{p} \\

先枚举 \(j\),把 \((b a^j, j)\) 存入 map;再枚举 \(i\),查 \(a^{im}\) 是否在 map 中。

cpp 复制代码
int BSGS(int a,int b,int p){
	a%=p;b%=p;
	if(b==1) return 0;
	int m=ceil(sqrt(p));
	int t=b;
	unordered_map<int,int> mp;
	for(int j=0;j<m;j++){
		mp[t]=j;
		t=t*a%p;
	}
	int mn=f(a,m,p);
	t=1;
	for(int i=1;i<=m;i++){
		t=t*mn%p;
		if(mp.count(t)) return i*m-mp[t];
	}
	return -1;
}

扩展 BSGS

当 \(\gcd(a,p) \ne 1\) 时,不断除以 \(\gcd\),直到互质,再 BSGS。


四、线性求逆元(批量)

问题

求 \(1\) 到 \(n\) 所有数模质数 \(p\) 的逆元,且 \(p > n\)。

递推公式

\inv\[i = (p - \lfloor p/i \rfloor) \times invp \\bmod i \bmod p \]

代码

cpp 复制代码
inv[1]=1;
for(int i=2;i<=n;i++)
    inv[i]=(p-p/i)*inv[p%i]%p;

推导要点

由 \(p = k i + r\),得 \(k i \equiv -r \pmod{p}\),两边乘 \(i^{-1} r^{-1}\):

\i\^{-1} \\equiv -k \\cdot r\^{-1} \\pmod{p} \\

因为 \(r < i\),\(r^{-1}\) 已算过,形成递推。


优先级排序

这是我认为的优先级:

排名 用途 出现频率
1 组合数取模 极高
2 解同余方程 高
3 BSGS 与扩展 BSGS 中
4 线性求逆元 中

一句话总结

From 大鲸鱼

这四个用途的共同核心都是:把模运算中的除法变成乘法。

  • 组合数:除以阶乘 → 乘阶乘逆元
  • 同余方程:除以 \(a\) → 乘 \(a^{-1}\)
  • BSGS:扩展 BSGS 中不断除以 \(\gcd\)
  • 线性求逆元:批量递推求 \(1\) 到 \(n\) 的逆元