数论

逆元

什么是逆元

在数论中,如果 ab1(modp)ab \equiv 1 \pmod{p} ,我们就说 aabb 在模 pp 意义下互为乘法逆元,记作 a=inv(b)a = inv(b)

逆元有什么用呢?

我们常常遇到一些题目要求结果对一个大质数 pp 取模,这是因为答案很大,出题人为了不麻烦大家写高精,就采取这样的方法。加减法和乘法对取模运算都是封闭的,所以你可以处处取模来避免溢出。

例如:

(12+3) % 10=5(\color{orange}{12} + \color{blue}{3}) \ \% \ 10 = \color{green}{5}

12 % 10=2\color{orange}{12} \ \% \ 10 = 2

3 % 10=3\color{blue}{3} \ \% \ 10 = 3

(2+3) % 10=5(2 + 3) \ \% \ 10 = \color{green}{5}

但遇到除法时,就麻烦了:

(12 ÷ 3) % 10=4(\color{orange}{12} \ \div \ \color{blue}{3}) \ \% \ 10 = 4

12 % 10=2\color{orange}{12} \ \% \ 10 = 2

3 % 10=3\color{blue}{3} \ \% \ 10 = 3

(2÷3) % 10= ? ? ?(2 \div 3) \ \% \ 10 = \ ? \ ? \ ?

为了解决模意义下的除法问题,我们引入了逆元。

inv(a) 其实可以看作模 p 意义下的 1a\color{red}{inv(a) \ 其实可以看作模 \ p \ 意义下的 \ \frac{1}{a}},也称为模 pp 意义下的 a1a^{-1}

a×inv(a)1(modp)a \times inv(a) \equiv 1 \pmod{p}

那么在模 pp 意义下, ab\frac{a}{b} 就可以变形为 a×inv(b)(modp)a \times inv(b) \pmod{p}

实际上在模 1010 意义下 inv(3)=7inv(3) = 7 ,所以上面的式子可以这样计算:

(12÷3) % 10=4(\color{orange}{12} \div \color{blue}{3}) \ \% \ 10 = \color{green}{4}

12 % 10=2\color{orange}{12} \ \% \ 10 = 2

3 % 10=3\color{blue}{3} \ \% \ 10 = 3

(2×inv(3)) % 10=4(2 \times inv(3)) \ \% \ 10 = \color{green}{4}

这里介绍三种计算逆元的方法:扩展欧几里得,费马小定理,线性递推。

扩展欧几里得

这个方法十分容易理解,而且对于单个查找效率似乎也还不错,比后面要介绍的大部分方法都要快(尤其对于 modp\bmod{p} 比较大的时候)。

这个就是利用拓欧求解线性同余方程 axc(modb)a*x \equiv c \pmod {b}c=1c=1 的情况。我们就可以转化为解 ax+by=1a*x + b*y = 1 这个方程。

先简单讲解一下欧几里德算法:

GCD(x,y)=GCD(x,y-x)

证明:

  • zxz|xzyz|y,则 z(yx)z|(y-x)

  • zz 不是 xx 的因子,则 zz 不是 xxyxy-x 的公因子。

  • zxz|xzz 不是 yy 的因子,则 zz 不是 xxyxy-x 的公因子。

  • 证毕。

GCD(x,y)=GCD(y,xy)=GCD(y,xby)=GCD(y,x % y)GCD(x,y)=GCD(y,x-y)=GCD(y,x-by)=GCD(y,x \ \% \ y)

int GCD(int x, int y)
{
	return y == 0 ? x : GCD(y, x % y);
}

这里简单讲解一下扩欧:

对于三个自然数 a,b,ca,b,c,求解 ap+bq=cap+bq=c(x,y)(x,y) 的整数解。

首先我们要判断是否存在解,对于这个这个存在整数解的充分条件是 gcd(a,b)cgcd(a,b) | c

也就是说 cca,ba,b 最大公因数的一个倍数。

但此时只需要求 c=gcd(a,b)c=gcd(a,b) 的情况就行了。

首先,解一定存在。

其次,因为 GCD(a,b)=GCD(b,a % b)GCD(a,b)=GCD(b,a \ \% \ b),所以 pa+qb=GCD(a,b)=GCD(b,a % b)=pb+qa % b=pb+q(aa/b % b)=qa+(pa/bq)bp*a+q*b=GCD(a,b)=GCD(b,a \ \% \ b)=p*b+q*a \ \% \ b=p*b+q*(a-a/b \ \% \ b)=q*a+(p-a/b*q)*b

根据前面的结论:aabb 都在减少,当 bb 减到 00 是,就可以得出 p=1p=1q=0q=0

然后递归回去就可以求出最终的 ppqq 了。

然后详细的求解过程就看这篇文章吧,点这里

这个做法还有个好处在于,当 apa \bot p(互质),但 pp 不是质数的时候也可以使用。

代码比较简单:

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

long long inv(long long a, long long p)
{
    long long x, y;
    if (exgcd(a, p, x, y) != 1)
        return -1;
    return (x % p + p) % p;
}

费马小定理

费马小定理是数论里的重要定理,叙述如下:

pp 是质数,且 gcd(a,p)=1gcd(a, p)=1 ,则有 ap11(modp)a ^ {p - 1} \equiv 1 \pmod{p}

费马小定理是欧拉定理的特殊情况。

欧拉定理如下:

如果 xxnn 互质,则 xϕ(n)1(modn)x^{\phi(n)} \equiv 1 \pmod{n}

ϕ(x)\phi(x) 表示与 xx 互质,且在 1(x1)1 \sim (x-1) 中的数的个数。

证明:

  • a1,a2,,aϕ(n)a_1,a_2,\sim,a_{\phi(n)} 为与 nn 互质,且在 1(n1)1 \sim (n-1) 中的数的个数。

  • 将序列 aa 所有数乘上 xx,则有 xa1,xa2,,xaϕ(n)xa_1,xa_2,\sim,xa_{\phi(n)}

  • 可得 xa1xa2xaϕ(n)a1a2aϕ(n)(modn)xa_1*xa_2*\sim*xa_{\phi(n)} \equiv a_1*a_2*\sim* a_{\phi(n)} \pmod{n}

  • 化简得 xϕ(n)a1a2aϕ(n)a1a2aϕ(n)(modn)x^{\phi(n)}*a_1*a_2*\sim*a_{\phi(n)} \equiv a_1*a_2*\sim* a_{\phi(n)} \pmod{n}

  • 两边同除以 a1a2aϕ(n)a_1*a_2*\sim* a_{\phi(n)},得 xϕ(n)1(modn)x^{\phi(n)} \equiv 1 \pmod{n}

  • 证毕。

考虑特殊情况,即 nn 为质数,则 ϕ(n)=n1\phi(n)=n-1,有 xn11(mod()n)x^{n-1} \equiv 1 \pmod(n),同时除以 xxxn2x1(mod()n)x^{n-2} \equiv x^{-1} \pmod(n)

上述即为费马小定理。

从逆元的定义推导,可得 a×inv(a)1ap1(modp)a \times inv(a) \equiv 1 \equiv a ^ {p - 1} \pmod{p},于是有 inv(a)ap2(modp)inv(a) \equiv a ^ {p - 2} \pmod{p}

于是对 ap2a ^ {p - 2} 算一下快速幂就好了。

注意这个方法只对 pp 是质数的情形有效。

inline long long Pow(long long a, long long n, long long p) // 快速幂
{
    long long ans = 1;
    while (n)
    {
        if (n & 1)
            ans = ans % p * a % p;
        a = a % p * a % p;
        n >>= 1;
    }
    return ans;
}

inline long long inv(long long a, long long p)
{
    return Pow(a, p - 2, p);
}

线性递推

例题

给定 n,pn,p1n1\sim n 中所有整数在模 pp 意义下的乘法逆元。

输入保证 pp 为质数,且 1n3×106n<p<200005281≤n≤3×10^6,n<p<20000528

如果我们要求一系列的乘法逆元,而且数据范围是 1n3×1061 \leq n \leq 3 \times 10 ^ 6,常规方法是行不通的。这里介绍逆元的线性递推求法(需保证 pp 是质数)。

p=aq+rp = aq + r, 即 q=paq = \left\lfloor \frac{p}{a} \right\rfloorr=pmodar = p \bmod a

在模 pp 意义下,有 aq+r0(modp)aq + r \equiv 0 \pmod{p}

因为 inv(x)inv(x) 可看作模 pp 意义下的 1x\frac{1}{x}

所以移项整理得 a=r×inv(q)(modp)a = -r \times inv(q) \pmod{p}

inv(a)=q×inv(r)(modp)inv(a) = -q \times inv(r) \pmod{p}

inv(a)=pa×inv(pmoda)(modp)inv(a) = -\left\lfloor \frac{p}{a} \right\rfloor \times inv(p \bmod a) \pmod{p}

#include <bits/stdc++.h>
#define _ (long long)3e6 + 5
using namespace std;

long long Inv[_];

inline long long mod(long long a, long long p)
{
    return (a % p + p) % p;
}

long long inv(long long a, long long p)
{
    if (Inv[a] || a == 0)
        return Inv[a];
    Inv[a] = mod(-p / a * inv(p % a, p), p);
    return Inv[a];
}

long long n, p;

signed main()
{
    scanf("%lld%lld", &n, &p);
    Inv[1] = 1;
    for (int i = 1; i <= n; ++i)
    {
        printf("%lld\n", inv(i, p));
    }
    return 0;
}
posted @ 2021-08-22 11:58  蒟蒻orz  阅读(4)  评论(0编辑  收藏  举报  来源