Loading

「学习笔记」逆元

定义

如果一个线性同余方程 \(ax \equiv 1 \pmod b\),则 \(x\) 称为 \(a \bmod b\) 的逆元,记作 \(a^{-1}\)

如何求逆元

利用费马小定理

前置知识:费马小定理。

「学习笔记」欧拉定理和费马小定理

如果 \(b\) 是质数,那么 \(a^{b - 1} \equiv 1\pmod b\),即 \(a \times a^{b - 2} \equiv 1 \pmod b\),逆元 \(x = a^{b - 2}\)

我们可以用快速幂来实现。

快速幂代码:

ll qpow(ll x, ll y, ll mod) {
    ll res = 1;
    while (y) {
        if (y & 1) {
            res = res * x % mod;
        }
        y >>= 1;
        x = x * x % mod;
    }
    return res % mod;
}

利用扩展欧几里得算法

前置知识:扩展欧几里得算法。

「学习笔记」扩展欧几里得定理与线性同余方程

利用扩展欧几里得算法求解,就是解不定方程 \(ax \equiv 1 \pmod b\)

\[ax \equiv 1 \pmod b\\ \Downarrow\\ ax + by = 1\\ \]

接下来按照扩展欧几里得算法解不定方程来做就行了。

\(ax + by \equiv c\) 的有解的要求是 \(c\)\(\gcd(a, b)\) 的倍数,因此欧几里得算法求逆元的要求是 \(a \perp b\)

扩展欧几里得算法的代码如下:

int exgcd(int a, int b, int& x, int& y) {
    if (!b) {
        x = 1, y = 0;
        return a;
    }
    int g = exgcd(b, a % b, y, x);
    y -= (a / b) * x;
    return g;
}

线性求逆元

\(1\) 的逆元为 \(1\),这是我们推算其他数的逆元的基础。

对于一个模数 \(p\),我们设 \(k = \left \lfloor \dfrac{p}{i} \right \rfloor, j = p \bmod i\),则 \(p = k \cdot i + j\),在 \(\pmod p\) 意义下,\(k \cdot i + j \equiv 0 \pmod p\),移项得 \(j \equiv -k \cdot i\),同余式两边同乘 \(j^{-1} \cdot i^{-1}\)\(i^{-1} \equiv -k \cdot j^{-1}\),将 \(k = \left \lfloor \dfrac{p}{i} \right \rfloor, j = p \bmod i\)\(i^{-1} \equiv - \left \lfloor \dfrac{p}{i} \right \rfloor \cdot (p \bmod i)^{-1}\),我们可以用 \(p - \left \lfloor \dfrac{p}{i} \right \rfloor\) 来代替 \(- \left \lfloor \dfrac{p}{i} \right \rfloor\) 这个负数。

最后的递推公式:\(i^{-1} \equiv \left ( p - \left \lfloor \dfrac{p}{i} \right \rfloor \right ) \cdot \left (p \bmod i \right )^{-1} \pmod p\)

代码:

#define rep(i, a, b, c) for (int i = (a); i <= (b); i += (c))
#define per(i, a, b, c) for (int i = (a); i >= (b); i -= (c))

const int N = 3e6 + 5;

int n, p;
ll inv[N];

int main() {
    n = read<int>(), p = read<int>();
    inv[1] = 1;
    rep (i, 2, n, 1) {
        inv[i] = 1ll * (p - (p / i)) * inv[p % i] % p;
    }
    rep (i, 1, n, 1) {
        cout << inv[i] << '\n';
    }
    return 0;
}

线性求任意 n 个数的逆元

给定任意 \(n\) 个数,要求线性时间复杂度求出这 \(n\) 个数的逆元。

先预处理出前 \(n\) 个数的前缀积 \(pro\),随后处理出 \(pro_n\) 的逆元 \(inv_n\),即 \(inv_n = (s_1 \cdot s_2 \cdot s_3 \dots s_n) ^ {-1}\)\(s_i\) 的逆元 \(s_i^{-1} = inv_n \cdot pro_{i - 1} \cdot \prod_{j = i + 1}^{n} s_j\),这样就能线性求出任意 \(n\) 个数的逆元了。

#define rep(i, a, b, c) for (int i = (a); i <= (b); i += (c))
#define per(i, a, b, c) for (int i = (a); i >= (b); i -= (c))

rep (i, 1, n, 1) {
    s[i] = read<ll>();
}
pro[0] = 1;
rep (i, 1, n, 1) {
    pro[i] = pro[i - 1] * s[i] % mod;
}
inv[n] = qpow(pro[n], mod - 2);
per (i, n, 1, 1) {
    inv[i - 1] = inv[i] * s[i] % mod;
}
rep (i, 2, n, 1) {
    inv[i] = inv[i] * pro[i - 1] % mod;
}

线性求阶乘的逆元

阶乘计算公式:\(n! = \prod_{i = 1}^{n} i\)\(0! = 1\)

与上面“线性求任意 \(n\) 个数的逆元”一样的道理,先预处理出阶乘 \(fac\),随后我们算出 \(fac_n\) 的逆元 \(inv_n (inv_n = n!^{-1})\),然后可以通过倒叙枚举来求出每个阶乘的逆元。

#define rep(i, a, b, c) for (int i = (a); i <= (b); i += (c))
#define per(i, a, b, c) for (int i = (a); i >= (b); i -= (c))

fac[0] = 1;
rep (i, 1, n, 1) {
    fac[i] = fac[i - 1] * i % mod;
}
inv[n] = qpow(fac[n], mod - 2);
per (i, n, 1, 1) {
    inv[i - 1] = inv[i] * i % mod;
}
posted @ 2023-08-29 17:22  yi_fan0305  阅读(16)  评论(0编辑  收藏  举报