乘法逆元
写在前面
在 \(OI\) 中,大多数情况下,善良的出题人为了避免高精度等大整数计算,常常会要求输出答案对一个数(大多是质数)取模的情况,但这衍生了一个问题:若题目中计算需用到除法而我们知道,除法是不能边除边取模的。
\(a \equiv b \pmod{p}\) 在大部分情况下 \(\lfloor\frac{a}{d}\rfloor \not\equiv \lfloor\frac{b}{d}\rfloor \pmod{p}\)
要解决这个问题,就需要用到一个概念:乘法逆元。
含义
什么是乘法逆元呢?
如果您到百度上搜索逆元,您会看到如下定义:(这里逆元素是逆元的全称)
逆元素是指一个可以取消另一给定元素运算的元素
---百度百科
这个定义似乎不太那么直观……
我们来举个例子吧,先再实数范围举例,由小学知识可知,如果一个代数式 \(F\) 乘一个数 \(a\) 后,再乘它的倒数 \(\frac{1}{a}\),相当于没有乘 \(a\)(这里不考虑 0 的情况),换句话说,我们乘 \(\frac {1} {a}\) 后,取消了代数式 \(F\) 乘 \(a\) 后值增大的影响。
不难发现这符合逆元的定义,故我们可以说一个数和其倒数互为乘法逆元。除此之外,我们还能发现一个数和其相反数互为加法逆元等等。
接下来回到代数式的例子,考虑为什么 \(a\) 的倒数 \(\frac{1}{a}\) 能消去乘 \(a\) 的影响。显然,是由于乘法结合律的存在,使得我们在运算 \(F \times a \times \frac {1} {a}\) 时可以先运算 \(a \times \frac {1} {a}\) 的值,再运算它和 \(F\) 的乘积,而 \(a \times \frac {1} {a} = 1\),任何数与 \(1\) 的乘积均为其本身,从而使乘 \(a\) 对 \(F\) 值增大的影响取消。
上文在实数范围内讨论了乘法逆元,现在我们来看本文主要讨论的内容,数论模意义下的乘法逆元。
由上文的分析来看,我们可以借助“任何数与 \(1\) 的乘积均为其本身”的性质定义模意义下的乘法逆元。
若 \(a \cdot b \equiv 1 \pmod{p}\) 那么我们称 \(b\) 是 \(a\) 在模 \(p\) 意义下的乘法逆元。
我们通常将 \(a\) 的逆元记作 \(a^{-1}\)。
如同 \(0\) 没有倒数一样,\(0\) 也没有乘法逆元。
不难发现,\(a\) 在模 \(p\) 意义下的乘法逆元均为 [1,p - 1] 中的整数。
计算逆元的方法
1. 费马小定理
费马小定理:若 \(p\) 为质数,且 \(a\) 与 \(p\) 互质,那么 \(a^{p-1} \equiv 1 \pmod{p}\)
对于定理中的式子,我们可以将两边同除 \(a\),得:
而 \(a^{-1}\) 就是 \(a\) 在模 \(p\) 意义下的乘法逆元。
故我们只需计算 \(a^{p - 2} \mod p\) 即可,而这个过程可用快速幂解决。
时间复杂度为 \(O(\log p)\)
2. 扩展欧几里德定理
定理内容:
a 和 b 的最大公约数是 gcd ,那么,我们一定能够找到这样的 x 和 y ,使得:
\(ax + by = gcd\)
不难发现 \(ax \equiv 1 \pmod {p}\) 可以写成 \(ax + py = 1\) 的形式:
将同余号换为等号得:
移项得:
由上式可知 \(ax - 1\) 为 \(p\) 的整数倍,不妨设其为 \(p\) 的 \(k(k \in Z)\) 倍:
继续移项得:
令 \(y = -k\),就写成了上文的形式:\(ax + py = 1\)
我们发现这个方程有解的条件是 \(gcd(a,p) \mid 1\),即 \(gcd(a,p) = 1\),\(a,p\) 互质,所以得出结论:\(a\) 在模 \(p\) 意义下的乘法逆元存在当且仅当 \(a,p\) 互质
接下来的问题就是如何求出 \(x,y\):
根据辗转相除法可得:
两式整合一下:
提取公因数 \(b\):
最终我们得到:
这样我们就能在递归求 \(gcd\) 的同时求出 \(x,y\)。
code:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll a,p,x,y;
ll exgcd(ll a,ll b,ll &x,ll &y)
{
if(b==0)
{
x=1; y=0; return a;
}
int gcd=exgcd(b,a%b,x,y);
int tmp=y;
y=x-a/b*y;
x=tmp;
return gcd;
}
int main()
{
scanf("%lld%lld",&a,&p);
exgcd(a,p,x,y);
x=(x%p+p)%p;//这里防止出现负数
printf("%d",x);
}
3. 线性求逆元
注意,这种求逆元的方法仅使用在 \(p\) 为质数的时候
我们用 \(inv[i]\) 来表示 \(i\) 的逆元
我们设 \(p = k \cdot i + r (r < i,1 < i <p)\)
再将这个式子放到 \(\pmod{p}\) 意义下就会得到
两边同时乘上 \(i^{-1} \cdot r^{-1}\) 就会得到
即
于是就可以从前面推出当前的逆元了,为了防止得到负数,在计算时 \((-\lfloor p/i \rfloor \cdot inv[p\%i]\%p +p)\%p\) 就可以了。
code:
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn = 3e6 + 10;
long long inv[maxn], n, p;
int main()
{
scanf("%lld%lld", &n, &p);
inv[0] = 0, inv[1] = 1;//初始化1的逆元为1
printf("%lld\n", inv[1]);
for (int i = 2; i <= n; ++i)
{
inv[i] = (p - p / i) * inv[p % i] % p;//上文中的式子
printf("%lld\n", inv[i]);
}
return 0;
}