简述逆元
逆元(Inverse element)就是在mod意义下,不能直接除以一个数,而要乘以它的逆元。 比如a∗b≡1(mod p),那么a,b互为模n意义下的逆元,比如你要算x/a,就可以改成x*b%p
观察a∗b≡1(mod p),变形为a∗b+k∗p=1,就可以用扩展欧几里得算法求a了,同时这里也说明了a和p只有在互素的情况下才存在逆元。
注意
在下面所有的算法中,最好先把除数取个模再运算。
方法一:扩展欧几里得算法
a∗b≡1(mod p) a∗b+k∗p=1 然后a就是我们要求的逆元,最终得到一个正数a的话就要对a mod p,因为a加上mp的时侯k减少mb可以使得等式依然 成立。
如果你不想让逆元为正数,那么直接返回x也是可以正确的逆元
代码
LL exgcd(LL a,LL b,LL &x,LL &y)//扩展欧几里得算法
{
if(b==0)
{
x=1,y=0;
return a;
}
LL ret=exgcd(b,a%b,y,x);
y-=a/b*x;
return ret;
}
LL getInv(int a,int mod)//求a在mod下的逆元,不存在逆元返回-1
{
LL x,y;
LL d=exgcd(a,mod,x,y);
return d==1?(x%mod+mod)%mod:-1;
}
方法二:费马小定理/欧拉定理
原理
费马小定理:若p为素数,则有a^(p−1)≡1(mod p) a^(p−2)∗a≡1(mod p) a^(p−2)就是a在mod p意义下的逆元啦。
欧拉定理:若a、p互素,则有a^φ(p)≡1(mod p)(费马小定理的一般形式) a^φ(p)∗a≡1(mod p) a^(φ(p)−1)就是a在mod p意义下的逆元啦。
代码
LL qkpow(LL a,LL p,LL mod)
{
LL t=1,tt=a%mod;
while(p)
{
if(p&1)t=t*tt%mod;
tt=tt*tt%mod;
p>>=1;
}
return t;
}
LL getInv(LL a,LL mod)
{
return qkpow(a,mod-2,mod);
}
方法三:递推求逆元
原理
可以这样考虑,设
t = ⌊ p/ i ⌋ t
k = p % i
则有
p = i × t + k
也就是
i × t + k ≡ 0 ( m o d p )
移项
i × t ≡ − k ( m o d p )
设i ′ 为i 对于模p的逆元,k ′ 为k 对于模p的逆元,两侧分别乘上i ′ k ′ ,得
i × t × i ′ × k ′ ≡ − k × i ′ × k ′ ( m o d p )
由于i × i ′ ( m o d p ) 为1,k × k ′ ( m o d p ) 为1,所以
t × k ′ ≡ − i ′ ( m o d p )
如果用数组inv表示逆元(inverse element),那么上式等价于 t × i n v [ k ] ≡ − i n v [ i ] ( m o d p )
将k和t代入,可得 ⌊ p i ⌋ × i n v [ p % i ] ≡ − i n v [ i ] ( m o d p )
也就是i n v [ i ] = − ⌊ p i ⌋ × i n v [ p % i ] % p
负数化正,得到i n v [ i ] = ( p − ⌊ p i ⌋ × i n v [ p % i ] % p ) % p
代码
LL inv[mod+5];
void getInv(LL mod)
{
inv[1]=1;
for(int i=2;i<mod;i++)
inv[i]=(m-mod/i)*inv[mod%i]%mod;
}
方法四:递归求逆元
代码
LL inv(LL i)
{
if(i==1)return 1;
return (mod-mod/i)*inv(mod%i)%mod;
}