乘法逆元

写在前面

OI 中,大多数情况下,善良的出题人为了避免高精度等大整数计算,常常会要求输出答案对一个数(大多是质数)取模的情况,但这衍生了一个问题:若题目中计算需用到除法而我们知道,除法是不能边除边取模的。

ab(modp) 在大部分情况下 adbd(modp)

要解决这个问题,就需要用到一个概念:乘法逆元。

含义

什么是乘法逆元呢?

如果您到百度上搜索逆元,您会看到如下定义:(这里逆元素是逆元的全称)

逆元素是指一个可以取消另一给定元素运算的元素

---百度百科

这个定义似乎不太那么直观……

我们来举个例子吧,先再实数范围举例,由小学知识可知,如果一个代数式 F 乘一个数 a 后,再乘它的倒数 1a,相当于没有乘 a(这里不考虑 0 的情况),换句话说,我们乘 1a 后,取消了代数式 Fa 后值增大的影响。

不难发现这符合逆元的定义,故我们可以说一个数和其倒数互为乘法逆元。除此之外,我们还能发现一个数和其相反数互为加法逆元等等。

接下来回到代数式的例子,考虑为什么 a 的倒数 1a 能消去乘 a 的影响。显然,是由于乘法结合律的存在,使得我们在运算 F×a×1a 时可以先运算 a×1a 的值,再运算它和 F 的乘积,而 a×1a=1,任何数与 1 的乘积均为其本身,从而使乘 aF 值增大的影响取消。

上文在实数范围内讨论了乘法逆元,现在我们来看本文主要讨论的内容,数论模意义下的乘法逆元。

由上文的分析来看,我们可以借助“任何数与 1 的乘积均为其本身”的性质定义模意义下的乘法逆元。

ab1(modp) 那么我们称 ba 在模 p 意义下的乘法逆元。

我们通常将 a 的逆元记作 a1

如同 0 没有倒数一样,0 也没有乘法逆元。

不难发现,a 在模 p 意义下的乘法逆元均为 [1,p - 1] 中的整数。

计算逆元的方法

1. 费马小定理

费马小定理:若 p 为质数,且 ap 互质,那么 ap11(modp)

对于定理中的式子,我们可以将两边同除 a,得:

ap2a1(modp)

a1 就是 a 在模 p 意义下的乘法逆元。

故我们只需计算 ap2modp 即可,而这个过程可用快速幂解决。

时间复杂度为 O(logp)

2. 扩展欧几里德定理

定理内容:

a 和 b 的最大公约数是 gcd ,那么,我们一定能够找到这样的 x 和 y ,使得:
ax+by=gcd

不难发现 ax1(modp) 可以写成 ax+py=1 的形式:

将同余号换为等号得:

axmodp=1modp

移项得:

(ax1)modp=0

由上式可知 ax1p 的整数倍,不妨设其为 pk(kZ) 倍:

ax1=kp

继续移项得:

axkp=1

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

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

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

ax+by=gcd

根据辗转相除法可得:

bx1+(aa/bb)y1=gcd

两式整合一下:

bx1+ay1a/bby1=ax+by

提取公因数 b:

ay1+b(x1a/by1)=ax+by

最终我们得到:

x=y1y=x1a/by1

这样我们就能在递归求 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=ki+r(r<i,1<i<p)

再将这个式子放到 (modp) 意义下就会得到

ki+r0(modp)

两边同时乘上 i1r1 就会得到

kr1+i10(modp)

i1kr1(modp)

i1p/i(p%i)1(modp)

inv[i]p/iinv[p%i](modp)

于是就可以从前面推出当前的逆元了,为了防止得到负数,在计算时 (p/iinv[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 @   「ycw123」  阅读(76)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示