学习NOTE番外 1——浅谈逆元
想必 OIer 们一定对逆元很熟悉,但是总会有像本蒟蒻一样的 dalao 不太清楚,所以我来写写我的认知与感受。
我还是太菜了。
逆元是什么
定义有点难说,相当于是:逆元就是某个运算下的“撤销键”。在模运算里,逆元就是让 \(a\times x\) 变回 \(1\) 的那个 。
比如说一个数的倒数,也就是 \(a\times \frac{1}{a} (a\in \mathbb{R})\)之类的。
我们教练是这么说的:
若 \(a,b\) 互质,且满足线性同余方程:
则称 \(x\) 为 \(a\) 模 \(b\) 的乘法逆元,记作 \(a^{-1}\)。
也就是说,在模 \(b\) 的意义下,\(a\) 乘以 \(x\) 的结果和 1 同余,那么 \(x\) 就是 \(a\) 的逆元。
综上,讲义里的“逆元”就是模意义下的乘法逆元,核心公式是:
前提是 \(\gcd(a,b)=1\)。
那为什么要互质呢?
因为方程
等价于存在整数 \(y\),使得
为什么是这个式子
我问了一下某只大鲸鱼。
大鲸鱼:
这是一个非常关键的问题,核心在于同余式和不定方程之间的等价转化。
我们从定义出发,一步一步来看。
1. 同余式的定义
同余式
的意思是:\(ax\) 和 \(1\) 除以 \(b\) 的余数相同。
用整除的语言来说,就是:
也就是说,\(ax - 1\) 是 \(b\) 的整数倍。
2. 把“整除”写成等式
既然 \(b \mid (ax - 1)\),那么一定存在某个整数,使得
这里 \(k\) 是某个整数。
我们把这个等式整理一下:
注意,\(k\) 只是一个整数,可以正也可以负。为了看起来更习惯,我们令
那么 \(y\) 也是整数,于是:
这就是:
等价于存在整数 \(y\),使得 \(ax + by = 1\)。
3. 反过来也成立
如果存在整数 \(x, y\) 使得
那么移项:
右边是 \(b\) 的整数倍,所以
根据同余的定义,这正是:
所以两边是完全等价的。
4. 为什么这个转化很重要?
因为同余方程
不太好直接解,但转化成不定方程
之后,就可以用扩展欧几里得算法来求整数解了。
而扩展欧几里得算法恰好就是用来求
的整数解的。当 \(\gcd(a,b)=1\) 时,右边就是 \(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\),得到不定方程 |
所以,同余方程和不定方程本质上是同一件事的两种写法。
当时我也没搞懂。
所以只有 \(a\) 和 \(b\) 互质 时,\(a\) 在模 \(b\) 下才有乘法逆元。不然你以为是啥。
逆元怎么求
目前有三种办法 (我学了的,我只学了这三种)。
法一:扩欧(扩展欧几里得算法)
先把
转化为不定方程:
然后用扩展欧几里得算法求出一组解 \((x_0, y_0)\)。
最后取
就是最小正整数逆元。
PID:P2613【模板】有理数取余
方法2:费马小定理求逆元
要求模数 \(b\) 是质数。
费马小定理说:
所以
因此
也就是说,当模数 \(p\) 是质数时,直接用快速幂算 \(a^{p-2} \bmod p\) 即可。
PID:P5431【模板】乘法逆元2
方法3:线性求逆元
问题:求 \([1,n]\) 中每个数模质数 \(p\) 的逆元,且 \(p>n\)。
令
其中
可以推出:
代码里为了防止负数,写成:
给一下对应代码:
inv[1]=1;
for(int i=2;i<=n;i++)
inv[i]=(p-p/i)*inv[p%i]%p;
PID:P3811【模板】乘法逆元、P2155 [SDOI2008] 沙拉公主的困惑
举个例子
解得 \(x = 2, 7, 12, \dots\)
因为
所以 \(8\) 模 \(5\) 的逆元是 \(2\)。
逆元可以用来干什么
一、组合数取模(最高频)
厚着脸皮 安利一下我的组合数博客
核心公式
预处理阶乘与阶乘逆元
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;
查询
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 定理)
二、解同余方程
形式
若 \(\gcd(a,p)=1\),则:
求单个逆元
// 扩展欧几里得
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;
}
// 费马小定理(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\),则:
先枚举 \(j\),把 \((b a^j, j)\) 存入 map;再枚举 \(i\),查 \(a^{im}\) 是否在 map 中。
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[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}\):
因为 \(r < i\),\(r^{-1}\) 已算过,形成递推。
优先级排序
这是我认为的优先级:
| 排名 | 用途 | 出现频率 |
|---|---|---|
| 1 | 组合数取模 | 极高 |
| 2 | 解同余方程 | 高 |
| 3 | BSGS 与扩展 BSGS | 中 |
| 4 | 线性求逆元 | 中 |
一句话总结
From 大鲸鱼
这四个用途的共同核心都是:把模运算中的除法变成乘法。
- 组合数:除以阶乘 → 乘阶乘逆元
- 同余方程:除以 \(a\) → 乘 \(a^{-1}\)
- BSGS:扩展 BSGS 中不断除以 \(\gcd\)
- 线性求逆元:批量递推求 \(1\) 到 \(n\) 的逆元

浙公网安备 33010602011771号