乘法逆元

前置芝士

欧几里得算法

欧几里得算法又称辗转相除法,用于计算两个正整数的最大公约数。

定理

(gcd(a,b)=gcd(b,a \% b)) (()(a>b)(r=a \% b,r) 不为 (0))

证明

(a=kb+r) ((a,b,k,r) 皆为正整数,且 (r<b)),则 (r=a \% b)

假设 (d)(a,b) 的一个公约数,记作 (d|a,d|b)

(r=a-kb),两边同时除以 (d)(frac{r}{d}=frac{a}{d}-frac{kb}{d}=m),由等式右边可知 (m) 为整数,因此 (d|r)

因此 (d) 也是 (b,a \% b) 的公约数。

假设 (d)(b,a \% b) 的公约数, 则 (d|b,d|(a-k*b),k) 是一个整数。

进而 (d|a)。因此 (d) 也是 (a,b) 的公约数。

因此 ((a,b))((b,a \% b)) 的公约数是一样的,其最大公约数也必然相等,得证。

代码

最终的 (a) 即为原 (a,b) 的最大公约数。

int gcd(int x, int y)
{
    int r;
    while(y != 0) r = x % y, a = y, x = r;
    return x;
}

或写成递归形式

int gcd(int x, int y) { return y ? gcd(y, x % y) : x; }

裴蜀定理

定理

(ax + by = c),则 (gcd(a,b)|c)

证明

(s=gcd(a,b)),则 (s|a)(s|b),所以 (s|(ax+by))

(s|c)。得证。

代码

「 洛谷 P4549 【模板】裴蜀定理 」

#include <iostream>
#include <cstdio>
#include <cmath>
using namespace std;
 
int n, a, ans = 0;
int gcd(int x, int y) { return y ? gcd(y, x % y) : x; }
 
int main()
 {
    scanf("%d", &n);
    for(int i = 1; i <= n; i++)
        scanf("%d", &a), ans = gcd(ans, abs(a));
    printf("%d", ans);
    return 0;
}

扩展欧几里得算法

定义

用于求 (ax+by=gcd(a,b)) 的一组解 (x,y)

解法

根据欧几里得算法,(gcd(a,b)=gcd(b,a \% b))。即求 (ax+by=bx_0+(a \% b)y_0)(其中 (x_0,y_0)已知)。

(a \% b=a-b*(a / b))(a / b) 表示 (lfloor frac{a}{b} floor))。

原式转化为:(ax+by=bx_0+(a-b*(a / b))y_0)

(ax+by=bx_0+ay_0-b*(a / b)y_0)

(ax+by=ay_0+b(x_0-(a / b)y_0))

于是得出 (x,y) 的一组可行解:(x=y_0,y=x_0-(a / b)y_0)。递归求解。

(b_n=0) 时,(gcd(a_n,b_n)=a_n),取 (x_n=1) 时等式必然成立。

例题

「 洛谷 P1082 同余方程 」

原式 (ax≡1(\% b)) 等同于求 (ax+by=1) 中最小的 (x)(y) 作为辅助答案)。

而根据裴蜀定理,可知 (gcd(a,b)|1),即 (gcd(a,b)=1)。所以该式有解的条件是 (a,b) 互质。

原式与 (ax+by=gcd(a,b)) 等价,满足扩展欧几里得算法,可以求出一组通解 (x,y)

题目要求 (x) 最小,显然 (x) 不断 (+b)(-b) 不会对答案产生影响,所以输出 (((x \% b)+b) \% b) 的值。

代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
using namespace std;
 
int a, b, x, y;
 
void exgcd(int a, int b)
{
    if(b == 0)
    {
        x = 1, y = 0;
        return ;
    }
    exgcd(b, a % b); 
    int tx = x;
    x = y, y = tx - (a / b) * y;
}
 
int main()
{
    scanf("%d%d", &a, &b);
    exgcd(a, b);
    printf("%d", (x % b + b) % b);
    return 0;
}

乘法逆元

定义

(ab≡1( \% p)) ,称 (b)(a) 关于 (1)(p) 的逆元,记做 (inv(a))

求解

费马小定理

费马小定理:当有两数 (a,p) 满足 (gcd(a,p)=1),有 (a^p≡a( \% p))

两边同除以 (a)(a^{p-1}≡1( \% p)),即 (a*a^{p-2}≡1( \% p))。用快速幂求出 (a^{p-2}) 即可。

代码

int poww(int b, int p, int k)
{
    int ans = 1;
    while(p)
    {
        if(p & 1) ans = (ans * b) % k;
        b = (b * b) % k;
        p >>= 1;
    }
    return ans;
}
 
int main()
{
    int a, p, inv_a;
    scanf("%d%d", &a, &p);
    inv_a = poww(a, p - 2, p);
}

扩展欧几里得算法

参照上面 「 例题 」 的解法求解。

递推求阶乘逆元

(inv_{i}=frac{1}{i!}( \% p))

(inv_{i+1}*(i+1)=frac{1}{(i+1)!}*(i+1)=frac{1}{i!}=inv_i)

可以先用费马小定理求出 (inv_n) 的值,再倒着递推。

代码

#include <iostream>
#include <cstdio>
using namespace std;
 
int max_num, inv[233333], f[2333333], mod = 1e9+7;
 
int poww(int b, int p, int k)
{
    int ans = 1;
    while(p)
    {
        if(p & 1) ans = (ans * b) % k;
        b = (b * b) % k;
        p >>= 1;
    }
    return ans;
}
 
int main()
{
    scanf("%d", &max_num);
    f[0] = 1;
    for(int i = 1; i <= max_num; i++) f[i] = (f[i - 1] * i) % mod;
    inv[max_num] = poww(f[max_num], mod - 2, mod);
    inv[0] = 1;
    for(int i = max_num - 1; i > 0; i--)
        inv[i] = (inv[i + 1] * (i + 1) % mod) % mod;
    return 0;
}

递推求逆元

(t=⌊frac{i}{p}⌋,k=p \% i)

(t*i+k≡0(\% p))

变形 (-t*i≡k(\% p))

两边同时除以 (ik)(-t*frac{1}{k}≡frac{1}{i})

(inv_i≡-t*inv_k)

所以 (inv_i=(p-⌊frac{i}{p}⌋)*inv_{p \% i} \% p)

参考文献

https://blog.csdn.net/leader_one/article/details/75222771

https://blog.csdn.net/qq_37656398/article/details/81434277

https://www.luogu.com.cn/blog/wlz123/solution-p4549

https://www.luogu.com.cn/problemnew/solution/P3811

原文地址:https://www.cnblogs.com/Andy-park/p/12088248.html