乘法逆元
引入
我们知道,模意义下没有同除性。
也就是说,我们无法直接完成除法。怎么办?
考虑使用乘另一个数代替除一个数。
乘法逆元
定义:若一个数 \(x\) 满足 \(ax\equiv 1\pmod b\),那么称 \(x\) 为模 \(b\) 意义下 \(a\) 的乘法逆元,记为 \(a^{-1}\)。
以下简称乘法逆元为逆元。
快速幂-费马小定理
费马小定理:对于 \(p\in\Bbb{P},a\in\Bbb{Z}\),有 \(a^p\equiv a\iff a^{p-1}\equiv 1\pmod p\)。
若 \(b\in\Bbb{P}\),有
故 \(a\) 在模 \(b\) 意义下的逆元即为 \(x=a^{p-2}\) 次方。
不理解的话结论也是很好记得罢。
局限性:要求模数 \(b\) 为质数。
Exgcd 求逆元
见我的这篇博文。
线性求 \([1,n]\) 所有数的逆元
若当前数为 \(i,i\in[1,n]\),求模 \(p\) 意义下的逆元(\(p\) 可不为质数),设
于是有
同时
两边同乘以 \(i^{-1}\times r^{-1}\),
将 \((3)\) 代入 \((5)\)
根据式 \((6)\),即可递推求逆元了。当然,它也可以单个求解,递归即可,复杂度 \(O(\log p)\),请读者自证。
为防止出现负数,一般改写为以下形式
代码
#include <iostream>
using namespace std;
typedef long long ll;
ll n,p;
ll inv[3114514];
void init()
{
inv[1]=1ll;
printf("1\n");
for(ll i=2;i<=n;i++)
{
inv[i]=(p-(p/i))%p*inv[p%i]%p;// 注意正负、取模
printf("%lld\n",inv[i]);
}
}
int main()
{
scanf("%lld %lld",&n,&p);
init();
return 0;
}
线性求任意 \(n\) 个数的逆元
更加一般化的求逆元法。
设这 \(n\) 个数为 \(a_i,i\in[1,n]\),它们的前缀积为 \(s_i=\prod_{j=1}^{i} a_i\) 及其逆元 \(sinv_i=(s_i)^{-1}\)。
显然 \(s_i\) 可以 \(O(n)\) 算出,且 \(sinv_n=(s_n)^{-1}\) 也可以 \(O(\log p)\) 用之前的方法求出。
接着倒序遍历每个 \(sinv_i,i\in[1,n)\),\(sinv_i=sinv_{i+1}\times a_{i+1}\)。这样就抵消了 \((a_{i+1})^{-1}\)。
于是对于每个数 \(a_i\),其逆元 \((a_i)^{-1}=sinv_{i}\times s_{i-1}\)。
于是利用前缀/差分的思想,巧妙地解决了问题。