乘法逆元详解
1. 什么是乘法逆元?
百度百科上是这样解释的:
乘法逆元,是指数学领域群 G 中任意一个元素 a ,都在 G 中有唯一的逆元 a',具有性质 a×a'=a'×a=e,其中 e 为该群的单位元。
不用看了,我知道你看不懂
逆元其实很简单,
若 \(a\cdot x\equiv1 \pmod {p}\),(一般 \(p\) 为质数)
则称 \(x\) 为 \(a\) 对 \(p\) 的逆元,记为 \(\operatorname{inv}(x)=a\)。
2. 乘法逆元能干嘛?
逆元一般用来求形如 \(\frac{a}{b} \bmod p\) 的式子的值,
设 \(k\) 为 \(b\) 对 \(p\) 的逆元,则有 \(\frac{a}{b} \bmod p=ak\bmod p\) 。
下面来看看这个式子是怎么推导的。
设 \(k\) 为 \(b\) 对 \(p\) 的逆元,即 \(bk\bmod p=1\),有:
3. 逆元怎么求?
(1) exgcd
还记得这个式子吗?\(ax\equiv 1 \pmod {p}\),
这个式子可以转化为 \(ax+py={1}\),(\(y\in\mathbb{Z}\))
这个时候就可以用 exgcd 来求逆元,
时间复杂度大约为 \(\mathcal O(\log n)\) 。
#include<bits/stdc++.h>
using namespace std;
void exgcd(int &x,int &y,int a,int b)
{
if(!b)
{
x=1,y=0;
return;
}
exgcd(x,y,b,a%b);
int t=x;
x=y;
y=t-a/b*y;
}
int main()
{
int n,p;//求n关于p的逆元
scanf("%d %d",&n,&p);
int x=0,y=0;
exgcd(x,y,n,p);
printf("%d",(x%p+p)%p);
return 0;
}
(2) 费马小定理
费马小定理:若 \(p\) 为素数,\(a\in\mathbb{Z^+}\),且 \(a\)、\(p\) 互质,则有 \(a^{p-1}\equiv 1(\bmod{p})\)。
这时发现上式和式子 \(ax\equiv 1 \pmod {p}\) 的右边部分都为 \(1\),
所以,\(ax\equiv a^{p-1}\pmod p\),
化简得 \(x\equiv a^{p-2} \pmod p\)。
如果 \(a^{p-2}\) 用快速幂计算,时间复杂度将和 exgcd 同级,为 \(\mathcal O(\log n)\)。
code:
#include<bits/stdc++.h>
using namespace std;
int FastPow(int a,int n,int mod)
{
int base=a,ans=1;
while(n)
{
if(n&1) ans=(base*ans)%mod;
base=(base*base)%mod;
n>>=1;
}
return ans;
}
int main()
{
int n,p;//求n关于p的逆元
scanf("%d %d",&n,&p);
printf("%d",FastPow(n,p-2,p));
return 0;
}
(3) 逆元的线性递推式
此处参考 https://zhuanlan.zhihu.com/p/100587745?utm_source=qq。
例如洛谷P3811,要求求区间 \([1,n]\) 的所有数的逆元,这个时候, exgcd 和费马小定理都显得有些“笨”。于是,就有了线性递推式。
假设我们现在想求 \(a\) 的逆元,为了方便之后的推导,我们将 \(p\) 表示成这样的形式: \(p=aq+r\)。
此处,\(q=\lfloor p/a\rfloor,r=p\bmod a\)。
容易看出,\(aq+r\equiv 0\pmod p\)。
移项整理可得 \(a\equiv -r\cdot \operatorname{inv}(q)\pmod p\)。
两边同时取倒数,得出 \(\operatorname{inv}(a)\equiv -q\cdot \operatorname{inv}(r)\pmod p\)。
再将 \(q=\lfloor p/a\rfloor,r=p\bmod a\) 代入,我们就得到了逆元的递推式 \(\operatorname{inv}(a)\equiv -\lfloor p/a\rfloor\cdot \operatorname{inv}(p\bmod a)\pmod p\)。
我们发现,式子中的 \(-\lfloor p/a\rfloor\) 有可能是负数,所以我们给它加一个 \(p\)。
所以,最终的递推式为:
真正实现的时候,将 \(\equiv\) 当作 \(=\) 即可。
然后就可以实现 \(\mathcal O(n)\) 递推啦。
code:
#include<bits/stdc++.h>
using namespace std;
const int N=10000010;
int n,p,inv[N];
int main()
{
scanf("%d%d",&n,&p);
inv[1]=1;
for(int i=2;i<=n;i++)
inv[i]=(p-p/i)*inv[p%i]%p;
printf("%d",inv[n]);
return 0;
}
4.参考资料
https://blog.csdn.net/yu121380/article/details/81188274
https://www.luogu.com.cn/blog/zjp-shadow/cheng-fa-ni-yuan