【算法笔记】乘法逆元
- 本文总计约 8000 字,阅读大约需要 30 分钟。
- 警告!警告!警告!本文有大量的 公式渲染,可能会导致加载异常缓慢!
前言
逆元可能是数论里面最最最最最重要的知识点了,因为大部分的 OI 题目里,都需要选手将答案对某个大质数(一般是 或者是 )取模,而在取模的过程中,因为取模不满足除法分配率,所以在遇见除法的时候,不能直接相除。
但这并不意味着我们对除法取模没有办法。而我们实现它的方法,就是使用乘法逆元来完成的。
题目引入
我们定义组合数 代表从 个互不相同的元素中,选择其中 个,有多少种选法。
计算组合数的公式为:,其中 代表 的阶乘,它的定义为 。
现在给你两个正整数 ,且 ,求 对 取模后的值。
暴力解法
对于求组合数问题,有两种基本的算法:
-
通过组合数公式 暴力硬算。
int
不够开long long
,long long
不够写高精。复杂度为 。然鹅如果 ,写高精似乎会超时 QwQ。这也是为什么我们要让答案对 取模,根据同余定理,有 。所以,无论 再怎么大,取模后的结果也总是能被一个
int
变量存储下。大部分 OI 题里,答案对大数取模,目的就是让输出的答案不要过大毕竟写高精是很麻烦的一件事 QwQ。但是我们会发现,计算组合数的过程中,需要使用除法。这里要强调一下:!例如,,但 ,两者显然不相等。所以,我们不能直接对除法运算取模。
-
可以通过杨辉三角来计算组合数。根据公式 ,我们可以存储先前已经算出来的组合数,然后递推地求出其它的组合数。
时间复杂度是 ,比暴力硬算的效率低,却可以绕过除法取模的问题。但当 的时候,好像会超时 QwQ,所以我们只能放弃掉这个算法了。
乘法逆元
除法取模
正如上面所说,杨辉三角的算法时间复杂度太低,所以我们还是得回到 的公式爆算中来。既然如此,我们就必须得直面除法取模的算法。
我们考虑最简单的情况,即 对 取模:
我们在 式两侧同时乘上一个 ,那么上式变为:
现在,让我们再考虑这个同余方程:
假如我们能够解出方程 中的 ,再将其代入到 中,就有了:
同时联立 ,我们就可以得出:
也就是说,假如我们可以求出上面方程 中的 的值,我们就可以通过 式求出 对 取模的结果了。
乘法逆元的概念
假如对于一个正整数 ,存在一个整数 ,使得同余方程 成立,那么我们称 为 在模 意义下的逆元,记做 。
它的作用是什么……当然就是向上面一样,解决除法取模的问题。因为除法是乘法的逆运算,我们就可以认为,在模 的意义下,除以一个整数,和乘以这个整数在模 意义下的逆元,效果是完全相同的。
乘法逆元的性质
我们对上面的乘法逆元的概念有了基本的了解,并且还可以通过观察,简单地得到一些乘法逆元的性质:
-
在模 意义下,任意一个数的乘法逆元一定小于 本身。
这个结论似乎很显然。因为我们不妨设 的乘法逆元为 ,且 ,那么一定有 ,所以 是 的乘法逆元,且 ,所以 的乘法逆元小于 。 -
在模 意义下整数 存在逆元,当且仅当 。
不妨设 ,则:稍微对 做一下变形,就有:
移项,得:
注意到这是一个关于 的二元一次不定方程。我们利用 Bézout 定理,可知, 存在解,当且仅当 ,即 ,命题得证。
同时,我们也可以得到另外一个结论:在模 的意义下, 都存在逆元,当且仅当 是一个质数。
-
若 是质数,那么在模 意义下, 的逆元为 , 的逆元为 ,除此以外, 到 之间,任何一个数和它的逆元一一对应。
这个结论又有一个高大上的名字,叫做 Wilson 定理:
当且仅当 是一个质数时,。
证明如下(数学证明警告!):
先证必要性,即 是一个质数:
若 不是一个质数,存在正整数 使得 ,显然,,。
当 时,,所以 ;
当 时,.
综上,此时 。所以必要性成立;
再证充分性,即 是一个质数 :
显然 时,命题成立;
对于任意质数 ,令 ,对于任意 ,令 。
任取 ,那么 ,且 ,由于 ,所以 ,所以对于任意 ,。
所以 中任意两个元素模 不同余。
设 ,则 。设 ,则:
,假设 ,则 ,即 ,与 矛盾;
同理,;
,假设 ,则 ,可知 ,解得 或 ,即 ,舍去。
所以 时,存在唯一的 ,使得 ,且任意两个 相互对应。
因此充分性成立。
接下来,在了解了这 条性质之后,我们就要想想乘法逆元的求法了。
乘法逆元的求法
说了这么多,我们现在已经知道了模数,那么我们应该怎么求一个数 的逆元呢?
当然可以暴力解同余方程 ,时间复杂度为 ,超时了别赖我 QwQ。
我们能不能用一种更快的算法来求逆元呢?当然可以,有下面的这 种求法供你选择:
扩展 Euclid 算法
我们考虑 的不定方程,其中 是 的逆元。
一说到不定方程,我们马上就应该想到:用扩欧啊!
于是,这段代码就此产生了:
//exgcd 求逆元
int inv(int a, int p, int & x, int & k) {
if(p == 0) {
x = 1;
k = 0;
}
else {
inv(p, a % p, k, x);
k -= a / p * x;
}
return (x % p + p) % p; //注意求出的逆元可能是负数,我们要将其变为正的
}
它的时间复杂度为 。
Fermat 小定理
Fermat 小定理是一个关于同余的问题,它的表述如下:
若 是质数且 ,则 。
下面给出证明(数学证明警告!):
我们考虑一系列数 ,由于 是质数,所以它们均与 互质。
假如 ,我们不妨设 ,可以证明它们模 两两不同余(证明方法可以参照上面 Wilson 定理的证明),所以它们对 取模后,调整顺序,依旧为 。
故 。
故 ,两边同时除以 ,得 。
当然,不看证明也是可以的。如果是要求逆元的话,我们只需要将这个定理小小地变形,则有 。于是有 ,我们可以通过快速幂来求出 对 取模的结果了,时间复杂度依旧为 。
线性求逆元
的算法似乎非常高效了,但是还不够高效。回到题目引入中的问题,如果 的话,就意味着我们需要至少 次计算阶乘中每一个数的逆元,每次都是 ,总复杂度为 ,在 的时候可能会被卡掉。
我们依旧需要尝试减少重复计算同一个逆元的次数,将其时间复杂度变为 。如下:
有一个很显然的结论:
因为商乘除数加余数等于被除数,所以有
代入 式,移项,得:
两边同乘 ,得:
根据乘法逆元的定义,我们有
由 式可知,如果我们已经求出了 到 的逆元,那么我们就可以通过 递推转移过来 的逆元,这个过程中,求出 的逆元的时间复杂度是线性的 ,而非 ,这样,就可以通过 大数据了。
代码如下:
int inv[p];
inv[1] = 1; //初始化 1 的逆元为 1
for(int i = 2; i <= p; ++i) {
inv[i] = (long long)(p - p / i) * (p % i) % p; //转移逆元
}
当然,虽然第三种算法的效率更高,但是它不大能用来求单个数的逆元,而且还浪费了 的辅助数组的空间复杂度。所以,选择哪种算法,要看具体题目情景里面是如何要求的。
有理数取模
那么,有了乘法逆元之后,我们就可以将取模的运算从整数推广到有理数。
假如一个有理数 ,我们要求 的值,其中 。根据 式,我们不难得出 。
也就是说,我们可以将取模运算推广到有理数,这样,就可以将其用于求概率或者数学期望时候的取模了。
例题
本题目列表会持续更新。
- 洛谷 P3811 【模板】乘法逆元
- 洛谷 P5431 【模板】乘法逆元 2
- 洛谷 P1082 [NOIP2012 提高组] 同余方程
- 洛谷 P2613 【模板】有理数取余
- 洛谷 P4071 [SDOI2016] 排列计数
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】