乘法逆元

写在前面

\(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^{p-2} \equiv a^{-1} \pmod{p} \]

\(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 \mod p = 1 \mod p \]

移项得:

\[(ax−1) \mod p = 0 \]

由上式可知 \(ax - 1\)\(p\) 的整数倍,不妨设其为 \(p\)\(k(k \in Z)\) 倍:

\[ax−1 = kp \]

继续移项得:

\[ax − kp = 1 \]

\(y = -k\),就写成了上文的形式:\(ax + py = 1\)

我们发现这个方程有解的条件是 \(gcd(a,p) \mid 1\),即 \(gcd(a,p) = 1\)\(a,p\) 互质,所以得出结论:\(a\) 在模 \(p\) 意义下的乘法逆元存在当且仅当 \(a,p\) 互质

接下来的问题就是如何求出 \(x,y\)

\[ax + by = gcd \]

根据辗转相除法可得:

\[bx_1 + (a - \lfloor a/b \rfloor \cdot b) y_1 = gcd \]

两式整合一下:

\[bx_1 + ay_1 - \lfloor a/b \rfloor \cdot by_1 = ax + by \]

提取公因数 \(b\):

\[ay_1+b (x_1 - \lfloor a/b \rfloor \cdot y_1) = ax + by \]

最终我们得到:

\[x = y_1,y = x_1 - \lfloor a/b \rfloor \cdot y_1 \]

这样我们就能在递归求 \(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}\) 意义下就会得到

\[k \cdot i+r \equiv 0\pmod{p} \]

两边同时乘上 \(i^{-1} \cdot r^{-1}\) 就会得到

\[k \cdot r^{-1} + i^{-1} \equiv 0 \pmod{p} \]

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

\[i^{-1} \equiv -\lfloor p/i \rfloor \cdot (p\%i)^{-1} \pmod{p} \]

\[inv[i] \equiv -\lfloor p/i \rfloor \cdot inv[p\%i] \pmod{p} \]

于是就可以从前面推出当前的逆元了,为了防止得到负数,在计算时 \((-\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;
}
posted @ 2022-10-06 16:48  「ycw123」  阅读(70)  评论(0编辑  收藏  举报