而如果存在一个元素 a 与另一元素 x,使得 a\circ x=e,那么 x 称为 a 在该系统中对于该运算的右逆元,记为 a^{-1}_{r}。反之,如果存在 x 满足 x\circ a=e,那么 x 称为 a 在该系统中对于该运算的左逆元,记为 a^{-1}_{l}。
而据我们所知,在整数系统中,存在相当多一部分的运算是左右对称的,即 a\circ x=x\circ a,那么我们简单地称 x 为 a 的逆元,记为 a^{-1}。对于我们目前研究的整数系统,存在最为常见的对称运算是加法与乘法。对于实数 x 来说,它的乘法逆元就是 \frac{1}{x},这也是我们记作 x^{-1} 的原因之一。
我们之所以要在模意义下求乘法逆元,是因为在取模的条件下,式子 (a\div b)\bmod c 并不等于 (a\bmod c)\div(b\bmod c),所以我们不得不使用乘法逆元来保证计算的正确性。
模意义下的乘法逆元
在模 p 意义下,如果 ax=1,那么 x 是 a 的乘法逆元,记为 a^{-1}。更加严谨地,存在:
ax\equiv 1\pmod p
为了使得题目更加容易求解,我们可以令 p 是质数,我们先来讨论这种情况。
Part 4 较高复杂度求解
费马小定理与快速幂
\bold{LinearExpectation}:费马小定理与欧拉定理的证明及应用
对于素数 p,当 \gcd(a,p)=1 时,存在:
a^{p-1}\equiv 1\pmod p
将原式替换进去,就可以得到:
a^{p-1}\equiv ax\pmod p
两边同时除以 a,可以得到:
x\equiv a^{p-2}\pmod p
显然,我们只需要求出 a^{p-2}\bmod p 即可。但是这样的一个式子,如果使用循环累乘,达到单个数 O(p) 的复杂度,就过于难看了,无法通过模板题。于是我们使用快速幂,以分治的思想进行解决,可以实现单个数 O(\log p)。对于单点求逆元,这已经是极其优秀的做法了。但是对于模板题所要求的 n 个连续整数逆元,这种做法的时间复杂度 O(n\log p) 则不是很容易被接受。
由于一切在模 p 意义下进行,所以存在 -\left\lfloor{\dfrac{p}{i}}\right\rfloor=p-\left\lfloor{\dfrac{p}{i}}\right\rfloor,通过这样我们可以避免负数的问题。
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,p,inv[20000530];
signed main(){
cin>>n>>p;
inv[1]=1;
for(int i=2;i<=n;++i)
inv[i]=(p-p/i)*inv[p%i]%p;
for(int i=1;i<=n;i++)
cout<<inv[i]<<'\n';
return 0;
}
然而实现方法并不止这一种,在我们得到 kj^{-1}+i^{-1}\equiv 0\pmod p 时就可以递归求得 i^{-1} 的解,为了防止重复递归,我们可以使用记忆化方法,这样依然能够保证每个逆元只被求取一遍,复杂度仍旧是 O(n)。
至此,我们完成了线性求逆元的过程,即 O(n) 求取 1\sim n 的逆元。
线性求任意逆元
其实要求任意给定的 n 个数的逆元也很简单。
给定 n 个数 a_i 满足 1\le a_i<p,我们求出来 n 个数的前缀积,也就是说对于任意的 i\in[1,n],存在:
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,p,inv[20000530]={0,1};
signed main(){
cin>>n>>p;
for(int i=2;i<=n;++i)inv[i]=(p-p/i)*inv[p%i]%p;
for(int i=1;i<=n;i++)cout<<inv[i]<<endl;
return 0;
}
同余方程
显然可以用快速幂法进行求解,我们只需要将 p-1 替换成 \varphi(b)-1 就好啦。但是我们注意到 b 的范围很大,所以不可能通过 O(b) 方法进行筛除,我们考虑用单点求 \varphi。同时辅助快速幂进行实现即可。
#include<bits/stdc++.h>
#define int long long
using namespace std;
int a,b;
int phi(int n){
int ans=1;
for(int i=2;i*i<=n;i++){
if(n%i==0){
n/=i;ans*=(i-1);
while(n%i==0)n/=i,ans*=i;
}
}if(n>1)ans*=(n-1);
return ans;
}int ksm(int base,int p){
int ans=1;
while(p){
if(p&1)ans=ans*base%b;
base=base*base%b;
p>>=1;
}return ans%b;
}signed main(){
cin>>a>>b;
int pb=phi(b);
cout<<ksm(a,pb-1)%b<<endl;
return 0;
}