乘法逆元

前置芝士

欧几里得算法

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

定理

\(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} \rfloor\))。

原式转化为:\(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

posted @ 2019-12-23 20:41  Nyxia  阅读(349)  评论(2编辑  收藏  举报