【OI学习笔记】数论-乘法逆元

这篇博客介绍了数论中的乘法逆元概念,包括其定义和作用。通过费马小定理和扩展欧几里得算法详细解释了如何在模意义下求解乘法逆元,还提到了线性求逆和离线求逆元等方法,适合信息学竞赛和算法学习者阅读。

板块:数论
前置知识:同余式、费马小定理、裴蜀定理
难度:中等
前置知识一览:

  • 同余式:对于式 a ≡ b (   m o d   m ) a\equiv b (\bmod m) ab(modm),意义是 ( a − b ) ∣ m (a-b)|m (ab)m,称 a a a b b b 对模 m m m 同余。
  • 费马小定理: a p − 1 ≡ 1 (   m o d     p ) a^{p-1}\equiv 1(\bmod \ p) ap11(mod p)
  • 裴蜀定理: ∀ a , b , ∃ x , y , s . t . a x + b y = gcd ⁡ ( a , b ) \forall a,b,\exists x,y,s.t.ax+by=\gcd(a,b) a,b,x,y,s.t.ax+by=gcd(a,b)

冷知识: ⊥ \perp 符号在数论中表示互质。

什么是乘法逆元-定义

逆元,全称逆元素,是指一个可以取消另一给定元素运算的元素。例如,对于 a x × 1 a ax\times \frac{1}{a} ax×a1,由于最后 x x x 的值 没有发生变化,所以我们称 a a a 1 a \frac{1}{a} a1 互为逆元。
a b ≡ a x (   m o d     p ) , b ⊥ p \frac{a}{b}\equiv ax (\bmod \ p),b\perp p baax(mod p),bp
对于图示的同余式,我们称 x x x b b b 在模 p p p 意义下的乘法逆元,记作 b − 1 b^{-1} b10没有乘法逆元。

如何求乘法逆元-解决问题

解决乘法逆元的方法有很多,常见的是费马小定理求乘法逆元、扩展欧几里得算法求乘法逆元、线性递推、离线求乘法逆元。
对乘法逆元定义式作如下推导:
由定义式两边同时乘 b b b 得, a ≡ a b x (   m o d     p ) a\equiv abx(\bmod \ p) aabx(mod p)
两边同时除以 a a a 得, b x ≡ 1 ( m o d   p ) bx\equiv1(mod \ p) bx1(mod p)
因此, a b ≡ a x (   m o d     p )    ⟺    b x ≡ 1 (   m o d     p ) \frac{a}{b}\equiv ax (\bmod \ p)\iff bx\equiv 1(\bmod \ p) baax(mod p)bx1(mod p)

费马小定理求乘法逆元

时间复杂度 O ( l o g p ) O(log p) O(logp)
已知费马小定理为 a p − 1 ≡ 1 (   m o d     p ) a^{p-1}\equiv 1(\bmod \ p) ap11(mod p),将 a p − 1 a^{p-1} ap1 拆解为 a × a p − 2 a\times a^{p-2} a×ap2,再结合乘法逆元推导式 a x ≡ 1 (   m o d     p ) ax\equiv 1(\bmod \ p) ax1(mod p) 观察可得逆元 x = a p − 2 x=a^{p-2} x=ap2 ,也就是说,求一个数的乘法逆元,相当于求这个数的 p − 2 p-2 p2 次方。当然,由于费马小定理的硬性要求,必须满足模数是质数才可以使用该方法

为了使计算次幂的速度更快,我们可以使用快速幂求解该问题。

参考代码如下:

#include <iostream>
#include <cstdio>

#define int long long

using namespace std;

int n, p;

int quickm (int a, int b, int p)
{
    int res = 1;
    while (b)
    {
        if (b & 1) res = res * a % p;
        b >>= 1;
        a = a * a % p;
    }
    return res;
}

signed main()
{
    cin >> n >> p;
    for (int i = 1; i <= n; i++)
    {
        cout << quickm(i, p - 2, p) << endl;
    }
    return 0;
}

扩展欧几里得算法(exgcd)求乘法逆元

(主要摘抄于参考资料[1],毕竟也是学习笔记嘛,如果原作者介意可以私信笔者删除)
时间复杂度 O ( l o g p ) O(log p) O(logp)
我们可以受 exgcd 的启发,将乘法逆元式 a x ≡ 1 (   m o d     p ) ax\equiv 1(\bmod \ p) ax1(mod p) 转化为 a x + b y = 1 ax+by=1 ax+by=1
对乘法逆元式作如下推导:
将其转化为: a x   m o d   p = 1   m o d   p ax \bmod p = 1 \bmod p axmodp=1modp
移项,得 ( a x − 1 )   m o d   p = 0 (ax-1)\bmod p=0 (ax1)modp=0
由此得, p ∣ ( a x − 1 ) p|(ax-1) p(ax1)
a x − 1 ax-1 ax1 p p p k k k 倍,即 a x − 1 = k p ax-1=kp ax1=kp,继续移项,得
a x − k p = 1 ax-kp=1 axkp=1
y = − k y=-k y=k,即可转化为 a x + p y = 1 ① ax+py=1① ax+py=1①,根据裴蜀定理,我们知道所得方程 ① ① 有解的条件是 gcd ⁡ ( a , p ) ∣ 1 \gcd(a,p)|1 gcd(a,p)∣1,即 gcd ⁡ ( a , p ) = 1 \gcd(a,p)=1 gcd(a,p)=1, a ⊥ p a\perp p ap。因此我们不难得出一个结论: a a a 在模 p p p 意义下的乘法逆元存在当且仅当 a , p a,p a,p 互质。

因为 gcd ⁡ ( a , p ) = 1 \gcd(a,p)=1 gcd(a,p)=1,所以可以直接套用 exgcd 求解,即求解 a x + p y = gcd ⁡ ( a , p ) ax+py=\gcd(a,p) ax+py=gcd(a,p) x x x 就是我们要求的乘法逆元。

参考代码:

#include <cstdio>
#include <cstring>
#include <iostream>

using namespace std;

typedef long long LL;

LL a, p, x = 0, y = 0;

LL exgcd(LL a, LL b, LL &x, LL &y)
{
    if (!b)
    {
        x = 1, y = 0;
        return a;
    }
    LL d = exgcd(b, a % b, y, x);
    y -= a / b * x;
    return d;
}

int main()
{
    scanf("%lld%lld", &a, &p);
    exgcd(a, p, x, y);
    printf("%lld", (x % p + p) % p); //防负数小妙招 
    return 0;
}

线性求逆

如果采用上面方法求解逆元,我们可以再 O ( n l o g p ) O(n log p) O(nlogp) 的时间复杂度内,求出 [ 1 , n ] ( 1 ≤ n ≤ p ) [1,n](1\leq n\leq p) [1,n](1np)的所有在模质数 p p p 意义下的乘法逆元,但如果针对下面这题的数据范围时,我们或许需要考虑更快速的方法,于是我们引入了线性求乘法逆元的方法。

基于逆元的定义,显然 1 在任意正整数模数意义下的乘法逆元都是它本身,设 p = k i + r ( r < i , 1 < i < p ) p=ki+r(r<i,1<i<p) p=ki+r(r<i,1<i<p),即 k i + r ki+r ki+r p p p 的倍数,转化为同余式得 k i + r ≡ 0 (   m o d     p ) ki+r\equiv 0(\bmod \ p) ki+r0(mod p)。作一下推导:
将同余式两边乘 i − 1 , r − 1 i^{-1},r^{-1} i1,r1,得 i − 1 r − 1 ( k i + r ) ≡ 0 (   m o d     p ) i^{-1}r^{-1}(ki+r)\equiv 0(\bmod \ p) i1r1(ki+r)0(mod p)
展开得, k r − 1 + i − 1 ≡ 0 (   m o d     p ) kr^{-1}+i^{-1}\equiv 0 (\bmod \ p) kr1+i10(mod p)
移项,得 i − 1 ≡ − k r − 1 (   m o d     p ) ① i^{-1}\equiv-kr^{-1}(\bmod \ p)① i1kr1(mod p)
p = k i + r p=ki+r p=ki+r,我们可以推导出 k = ⌊ p i ⌋ , r = p   m o d   i k=\lfloor \frac{p}{i} \rfloor,r=p \bmod i k=ip,r=pmodi,结合①式可得 i − 1 ≡ − ⌊ p i ⌋ × ( p   m o d   i ) − 1 (   m o d     p ) ② i^{-1}\equiv -\lfloor \frac{p}{i} \rfloor\times (p\bmod i)^{-1}(\bmod \ p)② i1ip×(pmodi)1(mod p)

余数小于除数,所以 p   m o d   i < i p \bmod i<i pmodi<i,所以 p   m o d   i p\bmod i pmodi 的逆元已经通过递推式②递推出来了,于是我们就可以通过这个式子推出 i − 1 i^{-1} i1

综上所述,预定义 1 的逆元为 1,再通过②式即可推出 [ 1 , n ] ( 1 ≤ n < p ) [1,n](1\leq n<p) [1,n](1n<p)中所有正整数在模一个质数 p p p 意义下的乘法逆元。

特别注意地, − ⌊ p i ⌋ -\lfloor \frac{p}{i} \rfloor ip 为负数,但在模 p p p 意义下,显然等价于 − ⌊ p i ⌋ + p -\lfloor \frac{p}{i} \rfloor+p ip+p,因此得出最终的递推式为:
i − 1 ≡ ( p − ⌊ p i ⌋ ) × ( p   m o d   i ) − 1 (   m o d     p ) i^{-1}\equiv (p-\lfloor \frac{p}{i} \rfloor)\times (p \bmod i)^{-1}(\bmod \ p) i1(pip⌋)×(pmodi)1(mod p)

模板链接已经给在上面的,参考代码如下:

#include <iostream>
#include <cstdio>

#define int long long

using namespace std;

typedef long long LL;

const int N = 3e6 + 10;

int inv[N], n, p;

int read ()
{
    int x = 0, f = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9')
    {
        if (ch == '-') f = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9')
    {
        x = (x << 1) + (x << 3) + (ch ^ 48);
        ch = getchar();
    }
    return x * f;
}

signed main()
{
    n = read(), p = read();
    inv[1] = 1;
    printf("1\n");
    for (int i = 2; i <= n; i++)
    {
        inv[i] = (LL)(p - p / i) * inv[p % i] % p;
        printf("%d\n", inv[i]);
    }
    return 0;
}

离线求逆元

pause at 2022/8/27,待更新

参考资料

[1]【洛谷日报#205】乘法逆元

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

oier_Asad.Chen

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值