内容部分参考于以下博客
https://www.cnblogs.com/rir1715/p/7748054.html#commentform
https://baijiahao.baidu.com/s?id=1609032096408414934&wfr=spider&for=pc
用途:
就博主现在的认知来说(毕竟是一个蒟蒻,如果有新的认识会更新的),逆元主要是用在除法取模的时候。首先,如果是乘法,那么有 (a*b)%mod = (a%mod*b%mod),这是显而易见的一个结论,但当我们遇到除法取模时(一般是在组合数取模的时候),就不会这么容易了,因为(a/b)%mod !=((a%mod)/(b%mod)),这个时候就需要我们的逆元登场了,至于为什么,请看下面逆元的定义。
定义:
简单来说,逆元其实就相当于初中学的倒数。
我们知道,对于一个数a,我们可以定义一个数b,使得 a*b = 1,这样的话,当我们需要除以a时,就可以转化为乘b,这样就能把除法转化为乘法。
同样,对于除a取模于mod的一个运算,我们依然可以定义一个数b,使得
(num/a)%mod = (num*b)mod,这样的一个数b,就可以称b为a在模mod下的一个逆元,有了逆元的话,我们就能将除法的取模运算转化为乘法的取模运算,从而来解决一些问题。
在这里给出逆元定义的数学形式(假设模数是n)
a * b ≡ 1 (mod n)
最后,要注意一点,一个数a在模n的情况下是不一定有逆元的,a关于n的逆元存在当且仅当a 和 n 互质,一个数a在模n的情况下逆元也不一定唯一,但我们只需要求出一个就够的,因为他们在模 n 的条件下结果是相同的。
求法:
目前,求解逆元一共有三种方法,接下来将一一讲解。
设 整数为 a,模数为 p,a在mod p条件下的逆元为 x。
1. 费马小定理
应用范围:p为素数。
这应该是最常用的的求逆元的方法,虽然有所局限,但确实是使用频率最高的一种算法了。
原理:
首先,我们由费马小定理得
a^(p - 1) ≡ 1 mod p
变形得
a * a^(p-2) ≡ 1 mod p
对比于逆元的定义
a * x ≡ 1 mod p
显然 我们可以得到
x ≡ a^(p-2) mod p
这样,我们就由费马小定理推理出 a 在 mod p 条件下的逆元为 a^(p-2)。
代码实现:
typedef long long LL
LL quick_pow(LL x, LL n LL p)// 快速幂求x^n mod p 的结果 { if(n==0) return 1; if(n==1) return x%p; LL ans = 1; LL tmp = x%p; while(n) { if(n&1) { ans = (ans*tmp)%p; } tmp = tmp*tmp%p; b>>=1; } return ans%p; } LL inv(LL b,LL p) //求数 b mod p 的逆元 { return quick_pow(b,p-2,p); }
2. 拓展欧几里得
局限:难以理解?(手动狗头)
原理:
敲起来太麻烦了,手写了
代码实现:
typedef long long LL LL ex_gcd(int a,int b,int &x,int &y)//注意x,y的传引用 { if(a==0&& b==0) return -1; if(b==0)//递归终止条件 { x = 1; y = 0; return a; } int ans = ex_gcd(b,a%b,x,y); int t = x; // 扩欧方程的解 x = y; y = x - a/b*y; return ans; } LL inv(LL b,LL p) //求数 b mod p 的逆元 { int x,y; int d = ex_gcd(b,p,x,y); if(d==-1) // b,p不互素,利用扩欧求不出逆元 return - 1; else return (x+p)%p; //x 的值就是 逆元,加上p再取模消除负数的情况 }
3. 线性推
原理:
因此我们可以通过一个递推式在线性时间内推导出1——(p-1)mod p 的逆元,递推式如下注意在式子右边加上p来消除负号
inv[i]=(p-p/i)*inv[p%i]%p;
代码实现:
typedef long long LL LL inv_arr[maxn]; void inv(LL b,LL p) //可以得到1到b mod p 的逆元 (1 < b < p) { inv_arr[1] = 1; for(int i = 2;i<=b;i++) inv_arr[i] = (p-p/i)*inv_arr[p%i]%p; return; }
如果还有其他疑问,欢迎在评论区留言