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


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

    • 同余式:对于式 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 
    #include 
    
    #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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    扩展欧几里得算法(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 
    #include 
    #include 
    
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    线性求逆

    如果采用上面方法求解逆元,我们可以再 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(rp=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 ipmodi<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[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 
    #include 
    
    #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;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    离线求逆元

    pause at 2022/8/27,待更新

    参考资料

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

  • 相关阅读:
    家政按摩预约小程序app应用场景功能介绍
    域内批量获取敏感文件
    Ims通话流程分析
    优先队列(priority_queue)用法详解
    【算法基础】(一)基础算法 --- 归并排序
    odoo 开发入门教程系列-准备一些操作(Action)?
    HTML 实现 点击按钮切换 整张界面 && 点击按钮切换局部界面
    1025 反转链表
    神经网络 深度神经网络,主流的神经网络的框架
    猿创征文|HCIE-Security Day53:Anti-DDoS设备简单谈
  • 原文地址:https://blog.csdn.net/lightningcs/article/details/126551745