参考博客:欧几里得算法和逆元
首先对于什么是逆元呢? 逆元存在的条件是在取模运算中,一个数的逆元就是这个数和他的逆元相乘后的乘积取模后的结果为1,既ax=1(mod p),则称x为a关于模p的逆元。因此这个特性可以让我们把数据比较大的除法运算转换算为乘法运算的取模。因此,逆元也是一个很重要的一个知识。
-逆元求法:
- 扩展欧几里得算法:
首先对与式子 ax+by=gcd(a,b); 对于该公式可以用扩展欧几里得公式求得。如果gcd(a,b)=1时,对公式两边同时取模b,则有ax≡1(mod b),所以x为a关于模b的逆元。附上代码:
void exgcd(int a,int mod,int &x,int &y)
{
if(mod==0){
x=1,y=0;return ;
}else
{
exgcd(mod,a%mod,y,x);
y-=a/mod*x;
}
}
int inv(int a)
{
int x,y;
exgcd(a,p,x,y);
return (x+mod)%mod;
}
2.费马小定理:
费马小定理:加入p是素数,且a和p互素,则存在以下公式 a^(p-1)≡1(mod p);因此a^(p-2)即为a关于p的逆元。不过由于我们做题的时候一般p会比较大,因此用快速幂的可能行会更高。
//快速幂求a关于MOD的逆元
int inv(int a){
int p=MOD-2;int ans=1;
while(p>0){
if(p&1)ans=ans*a%MOD;
a*=a;
p>>=1;
a%=MOD;
}
return ans;
}
3.线性求逆元:
线性逆元的递推公式:inv[i]=inv[Mmodi]×(M−M/i)modM
其中:i < M;
证明:
设 M = ki + b;
=> k = M / i, b = M mod i;
=> ki + b = 0 ( mod M);
=> ki = -b ( mod M);
=> 1 / ki = -1 / b ( mod M);
=> 1 / i = -k / b (mod M);
=> inv( i ) = inv( M mod i ) * ( M - M / i ) % M;
代码:
int inv(int a) {
return a == 1 ? 1 : (MOD - MOD / a) * inv(MOD % a) % MOD;
}
小结:
扩展欧几里得:要求a,p互为质数。时间复杂度O (log max( a ,p));
费马小定理:要求a,p互为质数,且p为素数。因此要求更高时间复杂度O (log p);
线性递推:由于是递推,因此如果是求n以内所有数的逆元的时间复杂度为O(n);
因此,如果是求一个数的逆元用扩展欧几里得算法会更好,如果求一个区间内很多数的逆元用线性递推更好。而且代码非常简单_。