逆元的求法总结

原文链接: https://blog.csdn.net/xiaoming_p/article/details/79644386

逆元

逆元(Inverse element)就是在mod意义下,不能直接除以一个数,而要乘以它的逆元。
比如 a ∗ b ≡ 1 ( m o d p ) a*b≡1(mod p) ab1(modp) a , b a,b a,b互为模 p p p意义下的逆元,就是当你想要求 x / a x/a x/a就可以改为求 x ∗ b % p x*b\%p xb%p
观察 a ∗ b ≡ 1 ( m o d p ) a*b≡1(mod p) ab1(modp),可以变形为 a ∗ b + k ∗ p = 1 a*b+k*p=1 ab+kp=1,就可以用扩展欧几里得算法求 a a a了,同时这里也说明了 a a a p p p只有在互素的情况下才存在逆元。

注意

在计算 a / b a/b a/b时,算出 b b b关于 m o d mod mod的逆元后,最好再对 a a a求一下 a % m o d a\%mod a%mod后再相乘 a ∗ i n v ( b ) % m o d a*inv(b)\%mod ainv(b)%mod,防止爆 l o n g l o n g long long longlong

扩展欧几里得求逆元模板

#include
#define ll long long
using namespace std;
const int N = 1e5 + 10;
ll a, mod = 1000000007, t;
ll ppow(ll a, ll b) {
    ll ans = 1;
    while(b > 0) {
        if(b & 1)
            ans = ans * a % mod;
        a = a * a % mod;
        b >>= 1;
    }
    return ans;
}
ll exgcd(ll a, ll b, ll &x, ll &y) {
    if(b == 0) {
        x = 1, y = 0;
        return a;
    }
    ll t = exgcd(b, a % b, y, x);
    y -= a / b * x;
    return t;
}
int main() {
    scanf("%lld", &t);
    while(t--) {
        scanf("%lld", &a);
        ll an, y;
        ll ni = ppow(2, a - 1);//求a关于mod的逆元
        ll gcd = exgcd(ni, mod, an, y);
        an = (an % mod + mod) % mod;//an就是求出来的逆元
        printf("%lld\n", (an * (a % mod)) % mod);//a%mod防止爆ll
    }
    return 0;
}

性能分析:

  • 时间复杂度:O(logn)(实际是斐波那契数列)
  • 适用范围:只要存在逆元即可求,适用于个数不多但是mod很大的时候,也是最常见的一种求逆元的方法。

费马小定理/欧拉定理求逆元模板

费马小定理:若 p p p为素数,则有 a p − 1 ≡ 1 ( m o d p ) a^{p-1}≡1(modp) ap11(modp)
a p − 2 ∗ a ≡ 1 ( m o d p ) a^{p-2}*a≡1(modp) ap2a1(modp)
a p − 2 a^{p-2} ap2就是 a a a在模 p p p意义下的逆元了。
欧拉定理:若a、p互素,则有 a φ ( p ) ≡ 1 ( m o d p ) a^{φ(p)}≡1(modp) aφ(p)1(modp)(费马小定理的一般形式)
a φ ( p ) ∗ a ≡ 1 ( m o d p ) a^{φ(p)}*a≡1(modp) aφ(p)a1(modp)
a φ ( p ) − 1 a^{φ(p)-1} aφ(p)1就是 a a a在模 p p 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);
}

性能分析:

  • O ( l o g m o d ) O(log^{mod}) O(logmod)
  • 适用范围:一般在mod是个素数的时候用,比扩欧快一点而且好写。
  • 但是如果是合数,相信一般没人无聊到去算个欧拉函数。

递推求逆元

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

注意:

  • 调用前要先预处理
  • 调用的时候要先对除数取 m o d mod mod

性能分析:

  • 时间复杂度 O ( n ) O(n) O(n)
  • 适用范围: m o d mod mod数是不大的素数而且多次调用,比如卢卡斯定理。

你可能感兴趣的:(数论)