跳转至

逆元

约 375 个字 81 行代码 预计阅读时间 2 分钟

定义

n意义下,一个数a可能有唯一的与之对应的乘法逆元x,使得a*x \equiv 1\pmod n

存在逆元的充分必要条件

gcd(a,n)=1,此时逆元唯一存在

用处

模n意义下,1个数a如果有逆元x,那么除以a相当于乘以x

即:(a/b)\%p=(a\%p*inv(b)\%p)\%p

求逆元方法

exgcd

时间复杂度

O(log_e n)

原理

求a的逆元相当于求解ax=1(mod n),

这个方程可以转化为ax-ny=1

然后套用求二元一次方程的方法,

用扩展欧几里得算法求得一组x_0,y_0和gcd

long long exgcd(long long a,long long b,long long &x,long long &y)
{
    if(!b)
    {
        x=1;
        y=0;
        return a;
    }
    long long r=exgcd(b,a%b,y,x);
    y-=x*(a/b);
    return r;
}
long long inverse_element(long long a,long long n)
{
    long long x,y;
    if(exgcd(a,n,x,y)==1ll)
        return (x%n+n)%n;
    else
        return -1;
}

欧拉定理

时间复杂度

O(\sqrt n+log_2n)

原理

a^{-1}\equiv a^{φ(n)-1} \pmod n

long long euler(long long n)
{
    long long res=n,a=n;  
    for(long long i=2;i*i<=a;i++)
    {  
        if(a%i==0)
        {  
            res=res/i*(i-1);//先除后乘是为了防止中间数据的溢出
            while(a%i==0)
                a/=i;
        }
    }
    if(a>1)
        res=res/a*(a-1);
    return res;
}
long long qpow(long long base,long long power,long long n)
{
    long long result=1;
    while(power>0)
    {
        if(power&1)
            result=result*base%n;
        base=base*base%n;
        power>>=1;
    }
    return result;
}
long long inverse_element(long long a,long long n)
{
    long long ie=qpow(a,euler(n)-1,n);
    if(ie*a%n==1ll)
        return ie;
    else
        return -1;//逆元不存在
}

费马小定理

时间复杂度

O(log_2n)

原理

a^{-1}≡a^{n-2} \pmod n

long long qpow(long long base,long long power,long long n)
{
    long long result=1;
    while(power>0)
    {
        if(power&1)
            result=result*base%n;
        base=base*base%n;
        power>>=1;
    }
    return result;
}
long long inverse_element(long long a,long long n)
{
    return qpow(a,n-2,n);
}

逆元打表

在模质数p下,求1~n逆元, n < p (p是奇质数)

时间复杂度

O(n)

原理

t=M/ik=M\%i

t*i+k\equiv 0 \pmod M

-t*i\equiv k \pmod M

-t*inv(k)=inv(i) \pmod M

inv(i)=(M-M/i)*inv(M\%i)\%M

inv(1)=1

const int N = 1e5 + 5;
ll inv[N];
    inv[1] = 1;
    for(int i=2; i<=n; ++i)
        inv[i] = (ll) (mod - mod / i) * inv[mod%i] % mod;

任意 n 个数

积的逆元等于逆元的积

首先计算 n 个数的前缀积,记为 s_i,然后用exgcd计算 s_n 的逆元,记为 sv_n

因为 sv_nn 个数的积的逆元,所以当我们把它乘上 a_n 时,就会和 a_n 的逆元抵消,于是就得到了 a_1a_{n-1} 的积逆元,记为 sv_{n-1}

同理我们可以依次计算出所有的 sv_i,于是 a_i^{-1} 就可以用 s_{i-1} \times sv_i 求得。

所以我们就在 O(n + \ln p) 的时间内计算出了 n 个数的逆元。

s[0] = 1;
for (int i = 1; i <= n; ++i)
    s[i] = s[i - 1] * a[i] % p;
ll sis = inv(s[n], p);
for (int i = n; i > 1; --i)
{
    inv[i]=sis*s[i-1]%p;
    sis = sis * a[i] % p;
}