一.概念阐述

先上图:

很明显,上述式子只有一条不成立,即 (a/b) % p,但是在有些情况下,又需要进行有模运算的除法。因为式子不成立,如果 a 过大或者 b 过大,极易导致计算过程丢失精度,造成结果误差。因此,为了杜绝这一现象,逆元出现了。

二.逆元定义

**==前提==:模 m 意义下,1个数 a 如果有逆元 x,那么除以 a 相当于乘以 x**

每个数 a 均有唯一的与之对应的乘法逆元 x,使得 a * x ≡ 1(mod p) ,即 (a * x) mod p = 1 mod p, 一个数有逆元的充分必要条件是gcd(a,p)=1,此时逆元唯一存在 。

三.理论铺垫

1.首先根据定义式:

a / x1 = a * x1^-1 //即 a/b = a*b^-1 ,基础乘除转换
inv[x1] ≡ x1^-1 (mod p) //inv[x1] 即为 x1 在模 m 情况下的逆元
a / x1 ≡ a * inv[x1] (mod p) //结论

2.a / x1 ≡ a * inv[x1] (mod p) 等式成立证明:(inv[x1] = x,即 x1 的逆元为 x)

逆元定义:
       a * x ≡ 1 (mod p)
       a * x mod p = 1,则称 x 为 a 模 p 的乘法逆元。
并且有 (a / x1) mod p = (a * x) mod p //(其中a / x1为整除)

证明:
假设: 
      a1 / x1 mod p = n (式子一)
(式子一两边同时乘上 x1)
      a1 mod p = n * x1 mod p (式子二)
(简化式子二)
      a1 ≡ n * x1 (mod p) (式子三)
(式子三两边同时乘上 x, x 为 1 / x1, 即假设 x 为 x1 的逆元)
(根据定义式) 
      x * x1 ≡ 1 (mod p) (式子四 ,因为 x 是 x1 的逆元,所以得出这个式子)
      a1 * x ≡ n * x1 * x (mod p) (式子五)
(式子四带入式子五)
      a1 * x ≡ n * 1 (mod p) (式子六)
(式子六化简)
      a1 * x ≡ n (mod p) (式子七)
(与式子七变形)
      a1 * x mod p = n mod p (式子七)
(式子一带入式子七)
      a1 * x mod p =  a1 / x1 mod p mod p (式子八)
(等式右边模了一次 p 之后,值肯定比 p 小,因此第二次模 p 无意义,式子八简化)
      a1 * x mod p =  a1 / x1 mod p (结论) (x 是 x1 的逆元,式子三定义的)
(证毕,有啥问题或者疑问的评论区提出)

四.逆元四大金刚

(瞎取的名字,看起来更加有逼格一点,别问为什么是五个,因为有一个是我,不说了,拯救世界去了。)




1. 费马小定理求逆元

费马小定理:如果p是一个质数,而整数a不是p的倍数,则有a^(p-1)≡ 1(mod p)

很明显,这玩意儿满足求逆元的前提条件,只不过有局限性,那就是 ==p 要为质数==

首先我们翻出我们之前准备好的结论:
                 a / x1 mod p =  a * x mod p (x 是 x1 在模 p 下的逆元) (式子一)
逆元定义:
                 x1 * x ≡ 1 (mod p) (式子二)
来来来,有眼睛的都发现了,我们把费马小定理的结论带入式子二
                 x1 * x = x1^(p-1) (式子三)    tips:(不懂的爬)
这个式子三很明显了吧,我们现在要求的是逆元 x ,这个化简就不用过多阐述了吧
                 x = x1^(p-2) (结论)

**由此可得,如果我们想求 x1 的 逆元 x ,那么在 p 为质数的情况下,我们可以用费马小定理求逆元,对于一个数 a 来说,它的逆元 ==inv[a] = a ^ (p-2)==.**

**当然,一般来说,题目给的质素都是超级无敌大,因此,接下来附上一份 ==快速幂==代码供各位太君食用**

#define mod 1e9 + 7  
template<typename T>  inline T quick_power(T base, T n)
{
    T res = 1;
    while (n > 0)
    {
        if (n & 1)
            //res *= base;
            res = (res * base) % mod;
        //base *= base;
        base = (base * base) % mod;
        n >>= 1;
    }
    return res;
}

2. 拓展欧几里得求逆元

前置知识:欧几里得算法

  • 欧几里得中有一个重要的定理: 对两个不全为0的非负整数不断应用此式:gcd(m,n)=gcd(n,m mod n);直到m mod n为0时。m就是最大公约数

来了来了(拓展欧几里得算法)

毫无疑问,欧几里得算法提供了一种快速计算最大公约数的方法

而扩展欧几里得算法不仅能够求出其最大公约数。而且能够求出m,n和其最大公约数构成的不定方程mx+ny=d的两个整
数x,y(这里x和y不一定为正数)。

在欧几里得算法中,终止状态是n == 0 时,这时候其实就是 gcd(m,0);我们想从这个最终状态反推出刚开始的状
态。由欧几里得算法可知。gcd(m,n) = gcd(n,m mod n);
        那么有如下表达式:

                      gcd(m,n) = m*x1+n*y1;

                gcd(n,m mod n) = n*x2+(m - m/n*n)*y2;
    (此处的m/n表示整除,例如:6/4 = 1;所以:m mod n = m % n = m - m/n*n)

化简上式有:
      n*x2 + m * y2 - m/n*n*y2 = m*y2 + n*(x2 - m/n*y2)
与原式 gcd(m,n) = m*x1+n*y1;对比,容易得出:
                      x1 = y2; y1 = x2 - m/n*y2;
根据上面的递归式和欧几里得算法的终止条件n == 0,我们可以很容易知道最终状态是m * x1 + 0 * y1 = m

==来来来,太君们吃点餐前小点==:

template<typename T> inline T ex_gcd(T a,T b,T &x,T &y) //拓展欧几里得 
{
    if(b==0)
    {
        x=1;
        y=0;
        return a;
    }
    T r = ex_gcd(b,a % b,x,y);
    T t = x;
    x = y;
    y = t - a / b * y;
    return r;
}

m*x+n*y=gcd(m,n); 这个方程也被称之为“贝祖等式”。它说明了对a,b和它们的最大公约数d之间的线性方程。

一定存在整数x,y使得m*x+n*y=gcd(m,n)成立。

从这里也可以得出一个重要推论:
                 a,b互质的充要条件是方程 ax+by = 1必有整数解。

现在来讨论一个更一般的方程:ax + by = c(a,b,c都是整数)。
这个方程想要有整数解,那么根据扩展欧几里得算法我们知道,当且仅当 m 是d = gcd(a,b)的倍数时有解 ,同时有
无穷多组整数解。

我们知道了线性方程 ax + by = c 有整数解的条件,并且根据上述算法,也能求出一组方程的解。但是
这组解很可能包含负数。

我们通常的需求是最小的特解。也就是这个不定方程的最小正整数解。

该了解的都了解了,那到底怎么用拓展欧几里得求逆元呢?

**把 a * x = 1( mod p)称为 a 关于 1 mod p 的乘法逆元。 它的解其实就相当于寻找方程 ax+py=1 (不理解的话重新看有整数解的判定条件)的解。根据乘法逆元的性质,==只有当 a 与 p 互素,a 关于模 p 的乘法逆元有解。如果不互素,则无解==。那么这个方程就是 a,b 互质的充要条件是方程 ax+by = 1 必有整数解。**

==等不及了吧,我就知道,快食用正餐吧==:

template<typename T> inline T mod_reverse(T a,T n) //ax=1(mod n) 求a的逆元x 
{
    T d,x,y;
    d=ex_gcd(a,n,x,y);
    if(d==1)
        return (x%n+n)%n;
    else
        return -1; //没有的话返回 -1
}

3. 线性递推求逆元

这玩意儿没有弯弯绕,就是推导的过程,略有点抽象(与我而言),直接上图:

看懂了不,没看懂就和我这个蒻鸡一起记板子吧(哭了)

// mod 是 质数
long long inv[maxn];
void getinv(ll mod)
{
    inv[1] = 1;
    for(int i = 2;i<=mod-1;i++)
        inv[i] = (mod-mod/i)*inv[mod%i]%mod;
}

4. 欧拉定理求逆元

欧拉定理:若a、p互素,则有a ^ φ( p) ≡ 1(mod p)(费马小定理的一般形式)
φ(( p ) - 1) ∗ a ≡ 1(mod p)
a ^ (φ( p )−1)就是 a 在mod p意义下的逆元啦。

至于φ( p )怎么推导计算的的,我这个蒻鸡实在理解不了-->> 太君移步


直接上板子:

template<typename T> inline T phi(T x) //欧拉函数
{
    T ans = x;
    for(int i = 2;i*i<=x;i++)
        if(x%i==0)
        {
            ans = ans/i*(i-1);
            while(x%i==0) x/=i;
        }
    if(x>1)
        ans=ans/x*(x-1);
    return ans;
}
long long Phi[max_n];
inline void euler(long long N) //欧拉筛法,批量求欧拉函数结果
{
    Phi[1] = 1;
    for(int i = 2;i<N;i++)
        if(!Phi[i])
            for(int j = i;j<N;j+=i)
            {
                if(!Phi[j])
                   Phi[j] = j;
                Phi[j] = Phi[j]/i*(i-1);
            }
}

**==够不够?不够再来一个==**

long long tot = 0; //质数个数
long long prime[maxn]; //质数
inline void Euler(long long N) //欧拉筛优化
{
    Phi[1] = 1;
    for(int i = 2;i<N;i++)
    {
        if(!Phi[i])
            Phi[i] = i-1,prime[tot++] = i;
        for(int j = 0;j<tot&&(long long)i*prime[j]<N;j++)
            if(i%prime[j])
                Phi[i*prime[j]] = Phi[i]*(prime[j]-1);
            else
            {
                Phi[i*prime[j]] = Phi[i]*prime[j];
                break;
            }
    }
}

五.阶乘逆元

顾名思义,就是阶乘的逆元,顺道提一下,可能有助于排列组合的计算,没啥好说的,继续上板子:

//阶乘逆元
#define ll long long
ll fac[1000000+5];        //阶乘
ll inv[1000000+5];        //逆元 
void getfac()
{
    fac[0] = inv[0] = 1;
    for (int i = 1 ; i <= 1000000 ; i++)
    {
        fac[i] = fac[i-1] * i % mod;
        inv[i] = quick_power(fac[i],mod-2);  
        //表示i的阶乘的逆元 
    }
}
//组合数
inline ll getC(ll n,ll m)//C(n,m) = n!/((n-m)!*m!) % mod
{
    return fac[n] * inv[n-m] % mod * inv[m] % mod;
}
//当 n,m 过大时,可以用Lucas定理降数据
inline ll Lucas(ll n,ll m)
{
    if(n < mod && m < mod) return getC(n, m);
    return Lucas(n/mod, m/mod) * getC(n%mod, m%mod)%mod;
}
---------------------------------------------------
//排列数
inline ll getA(ll n,ll m) //A(n,m) = n!/(n-m)! % mod
{
	return fac[n] * inv[n-m] % mod;
}

六.小结

一般,对于用到逆元的题目,p 一般都为质数,因此个人建议先把费马小定理求逆元先掌握,所以我对费马小定理求逆元的推导做的十分详细,如果遇到 p 不是质数,那么建议直接上欧拉求逆元。最后呢,可以根据实际情况选用线性递推求逆元,或者阶乘逆元。
相关例题,大家可以去 牛客 啊,洛谷 之类的一些OJ上找,直接搜知识点就行,看完要巩固。

有啥问题的,欢迎指出,我好改正。