乘法逆元入门(四种方法及补充)

原因

在一些题目中,因为数据量会特别大甚至超过ll,所以会要求最后结果mod一个数,实际上就是让你在计算过程中就要不断mod
对于加法: ( a + b ) % m = ( a % m + b % m ) % m (a+b)\%m = (a\%m+b\%m)\%m (a+b)%m=(a%m+b%m)%m
对于减法: ( a − b ) % m = ( a % m − b % m ) % m (a-b)\%m = (a\%m-b\%m)\%m (ab)%m=(a%mb%m)%m
对于乘法: ( a ∗ b ) % m = ( a % m ∗ b % m ) % m (a*b)\%m = (a\%m*b\%m)\%m (ab)%m=(a%mb%m)%m
但是这个规则在除法不适用:简单例子比如 ( 30 7 ) % 2 (\frac{30}{7})\%2 (730)%2
为了对除法也能进行模运算,就需要乘法逆元

什么是乘法逆元

若c是b的逆元,则有 b ∗ c ≡ 1 ( m o d m ) b*c≡1(mod m) bc1(modm),称c为b关于m的乘法逆元
例如 b = 10 , m = 3 b=10,m=3 b=10,m=3
c = 4 c=4 c=4
a = 20 , ( 20 / 10 ) % 3 = 2 , ( 20 ∗ 4 ) % 3 = 2 a=20, (20/10)\%3=2 ,(20*4)\%3=2 a=20,(20/10)%3=2,(204)%3=2
a = 40 , ( 40 / 10 ) % 3 = 1 , ( 40 ∗ 4 ) % 3 = 1 a=40, (40/10)\%3=1 ,(40*4)\%3=1 a=40,(40/10)%3=1,(404)%3=1

现在我们要求(a/b)%m,可以找到一个c使得(a/b)%m=(a*c)%m

求法

1.费马小定理

如果p是一个质数,而整数a不是p的倍数,则有a(p-1)≡1(mod p)

所以可得a(p-2) ≡ a-1(mod p)

换成代码如下

(a/b)%m
(a*pow(b,m-2))%m

这里还需要快速幂来配合计算

ll quick_pow(ll x,ll n,ll m)
{
	ll res = 1;
	while(n > 0)
	{
		if(n & 1)
            res = res * x % m;
		x = x * x % m;
		n >>= 1;
	}
	return res;
}  
ll inv(ll a)
{
    return quick_pow(a,mod - 2,mod);
}

2.扩展欧几里得

扩展欧几里得算法(英语:Extended Euclidean algorithm)是欧几里得算法(又叫辗转相除法)的扩展。已知整数a、b,扩展欧几里得算法可以在求得a、b的最大公约数的同时,能找到整数x、y(其中一个很可能是负数),使它们满足贝祖等式ax+by=gcd(a,b)

贝祖定理:即如果a、b是整数,那么一定存在整数x、y使得ax+by=gcd(a,b)。
如果a是负数,可以把问题转化成|a|(-x)+by=gcd(|a|,b),然后令x’=(-x)。

ll gcdEx(ll a,ll b,ll &x,int &y)
{
    if(b==0)
    {
        x=1,y=0 ;
        return a ;
    }
    else
    {
        int r=gcdEx(b,a%b,x,y);
        /* r = GCD(a, b) = GCD(b, a%b) */
        int t=x ;
        x=y ;
        y=t-a/b*y ;
        return r ;
    }
}

gcd(a,m)=1!!!
当y=0,逆元即ax+my=1 (mod m)中的x
利用欧几里得算法不断递归直到x=1,y=0—>反向递归求出第一层的x和y,x即为a模m的逆元。

ll inv(ll a,ll m)
{
    ll x,y;
    if(gcdEx(a,m,x,y)!=1)
        return -1;//不互质 不存在逆元
    return (x%m+m)%m;
}
3.递归

当m是质数
inv(a) = (m - m / a) * inv(m % a) % m
暴力反向递归

ll inv2(ll a,ll m)//代入a%m m
{
    return a==1?1:(m-m/a)*inv2(m%a,m)%m;
}
4.线性递推

求关于一个m的多个逆元,道理差不多

ll m=3;
ll inv[m+5];
void inv3(ll m)
{
    inv[1]=1;
    for(int i=2;i<m;i++)
        inv[i]=(m-m/i)*inv[m%i]%m;
}

除了以上几种,还有穷举、二进制扩展、牛顿迭代法等等
以下是关于以上方法的证明:
费马小定理更多
扩展欧几里得更多
递归更多
线性递推更多

你可能感兴趣的:(c++,算法,乘法逆元)