这里以一道求乘法逆元的模板题(【洛谷3811】【模板】乘法逆元)为例,来讲一讲求一个数乘法逆元的三种经典解法。
解法一:快速幂
证明:费马小定理。由费马小定理 可得, ,显然,我们可以发现 就是 在模 意义下的逆元。只要用快速幂就可以快速求出。
代码:
#include<bits/stdc++.h>
#define max(x,y) ((x)>(y)?(x):(y))
#define min(x,y) ((x)<(y)?(x):(y))
#define LL long long
#define swap(x,y) (x^=y,y^=x,x^=y)
using namespace std;
int n,m;
inline char tc()
{
static char ff[100000],*A=ff,*B=ff;
return A==B&&(B=(A=ff)+fread(ff,1,100000,stdin),A==B)?EOF:*A++;
}
inline void read(int &x)
{
x=0;int f=1;char ch;
while(!isdigit(ch=tc())) f=ch^'-'?1:-1;
while(x=(x<<3)+(x<<1)+ch-'0',isdigit(ch=tc()));
x*=f;
}
inline void write(int x)
{
if(x<0) putchar('-'),x=-x;
if(x>9) write(x/10);
putchar(x%10+'0');
}
inline int quick_pow(LL x,LL y)
{
LL res=1;
while(y)
{
if(y&1) (res*=x)%=m;
(x*=x)%=m,y>>=1;
}
return (res+m)%m;
}
int main()
{
register int i;
for(read(n),read(m),i=1;i<=n;++i) write(quick_pow(i,m-2)),putchar('\n');
return 0;
}
解法二:扩展欧几里得(exgcd)
证明:我们可以把扩欧的式子 中的 当作 , 与 互质, ,即 ,两边同时对 取模,得 % % % ,化简得 。将 代替 ,我们就可以得到这个式子的解 就是 关于 的逆元。
代码:
#include<bits/stdc++.h>
#define max(x,y) ((x)>(y)?(x):(y))
#define min(x,y) ((x)<(y)?(x):(y))
#define LL long long
#define swap(x,y) (x^=y,y^=x,x^=y)
using namespace std;
int n,m;
inline char tc()
{
static char ff[100000],*A=ff,*B=ff;
return A==B&&(B=(A=ff)+fread(ff,1,100000,stdin),A==B)?EOF:*A++;
}
inline void read(int &x)
{
x=0;char ch;
while(!isdigit(ch=tc()));
while(x=(x<<3)+(x<<1)+ch-'0',isdigit(ch=tc()));
}
inline void write(int x)
{
if(x<0) putchar('-'),x=-x;
if(x>9) write(x/10);
putchar(x%10+'0');
}
inline void exgcd(int x,int y,int &s1,int &s2)
{
if(!y) {s1=1,s2=0;return;}
exgcd(y,x%y,s2,s1),s2-=x/y*s1;
}
inline int Inv(int x,int y)
{
int s1,s2;
exgcd(x,y,s1,s2);
return (s1%m+m)%m;
}
int main()
{
register int i;
for(read(n),read(m),i=1;i<=n;++i) write(Inv(i,m)),putchar('\n');
return 0;
}
解法三:线性求逆元
证明:我们可以得到 % ,将原式两边同乘 % 得 % ,移项得 % 。
于是,我们就可以通过递推来从前面已求出的逆元推出当前的逆元了。
代码:
#include<bits/stdc++.h>
#define max(x,y) ((x)>(y)?(x):(y))
#define min(x,y) ((x)<(y)?(x):(y))
#define LL long long
#define swap(x,y) (x^=y,y^=x,x^=y)
#define N 3000000
using namespace std;
int n,m,Inv[N+5];
inline char tc()
{
static char ff[100000],*A=ff,*B=ff;
return A==B&&(B=(A=ff)+fread(ff,1,100000,stdin),A==B)?EOF:*A++;
}
inline void read(int &x)
{
x=0;int f=1;char ch;
while(!isdigit(ch=tc())) f=ch^'-'?1:-1;
while(x=(x<<3)+(x<<1)+ch-'0',isdigit(ch=tc()));
x*=f;
}
inline void write(int x)
{
if(x<0) putchar('-'),x=-x;
if(x>9) write(x/10);
putchar(x%10+'0');
}
int main()
{
register int i;
for(read(n),read(m),write(Inv[1]=1),putchar('\n'),i=2;i<=n;++i)
write(Inv[i]=((-1LL*Inv[m%i]*(m/i))%m+m)%m),putchar('\n');
return 0;
}