线性求逆元

线性求逆元

时间复杂度:\(O(n)\)

问题:求取\(1...n\)关于质数\(p\)的逆元。

应用举例:求取组合数\(C_n^m \ mod \ p\),其中\(1 \leq n,m\leq10^7,p = 998244353\)

\[C_n^m \equiv \frac {n!} {(n-m)!m!} (mod \ p) \]

\[C_n^m\equiv n! * (n-m)!^{-1}*m!^{-1} (mod\ p) \]

思路一:

假设我们求取\(n\)关于质数\(p\)的逆元,即求取\(n^{-1}\)

我们设\(a = \lfloor\frac{p}{n}\rfloor,b = p\ mod\ n\)。那么就有

\[a*n + b \equiv 0(mod\ p)\tag{1} \]

对公式\((1)\)移项可得:

\[\begin{align*} a*n &\equiv -b(mod\ p)\\ -\frac{a}{b} &\equiv n^{-1} (mod\ p) \end{align*} \]

即:

\[\begin{align*} n^{-1} &\equiv -\frac{a}{b} (mod\ p)\\ &\equiv -\frac{\lfloor \frac{p}{n} \rfloor}{p\ mod\ n} (mod\ p)\\ &\equiv -\lfloor \frac{p}{n} \rfloor\cdot(p\ mod\ n)^{-1}(mod\ p)\\ &\equiv (p-\lfloor \frac{p}{n} \rfloor)\cdot(p\ mod\ n)^{-1}(mod\ p)\\ \end{align*} \]

所以:

\[n^{-1} \equiv (p-\lfloor \frac{p}{n} \rfloor)\cdot(p\ mod\ n)^{-1}(mod\ p) \]

无论\(p\)是多少,\(1\)的逆元就是\(1\).这是边界条件。

代码:

inv[1] = 1;
for(int i = 2;i<=n;i++)
{
    inv[i] = (ll)(p - p/i)*(inv[p%i]) % p;
}

思路二:

\(f[i]=i!\)

我们设前缀积的逆元为\(invs[i] = f[i]^{-1}\)对于模数\(p\)而言。

\(i\)个数的逆元:

\[\begin{align*} inv[i] &\equiv \frac {1}{i}(mod\ p) \\ &\equiv \frac{(i-1)!}{i!} (mod\ p)\\ &\equiv \frac {f[i-1]}{i!} (mod\ p)\\ &\equiv f[i-1] * invs[i] \end{align*} \]

更新\(invs\):

\[invs[i-1] \equiv i * invs[i] (mod\ p) \]

代码:

f[0] = 1;
for(int i = 1;i<=n;i++)
{
    f[i] = (f[i-1] * i) % mod;
}
invs[i] = qmi(f[i],p-2);
for(int i = n;i>0;i--)
{
    inv[i] = invs[i] * f[i-1] % mod;
}

乘法逆元

解题思路:

模板题,如上所述.

代码:

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int mod = 1e9 + 7;
void solve() {
    int n, p;
    scanf("%d %d", &n, &p);
    vector<ll> inv(n + 1);
    inv[1] = 1;

    for (int i = 2; i <= n; i++) {
        inv[i] = (p - (p / i)) * inv[p % i] % p;
    }

    for (int i = 1; i <= n; i++) {
        printf("%lld\n", inv[i]);
    }
}

int main() {
    int t = 1;

    while (t--) {
        solve();
    }

    return 0;
}

乘法逆元 2

解题思路:

对应思路二,我们会发现,其实前缀积中的数不一定要是\(1 \sim n\)

只是当着\(n\)个元素为\(1 \sim n\)的时候,他们的前缀积是\(n!\).如果我们元素进行替换,带入前缀积,式子依旧成立。

我们可以通过这种方法线性求取任意\(n\)个元素的逆元。

\[\begin{align*} inv[i] &\equiv \frac{a[1]*a[2] *...*a[i-1]}{a[1]*a[2]*...*a[i]}(mod\ p)\\ &\equiv f[i-1] * invs[i] (mod\ p) \end{align*} \]

更新\(invs\):

\[invs[i-1] \equiv a[i] * invs[i] (mod\ p) \]

代码:

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int mod = 998244353;

ll qmi(ll a, ll b, ll p)
{
    ll res = 1;
    while (b)
    {
        if (b & 1)
        {
            res = (res * a) % p;
        }
        a = (a * a) % p;
        b >>= 1;
    }
    return res;
}

void solve()
{
    int n;
    int p = 1e9 + 7;
    scanf("%d", &n);
    vector<ll> a(n + 1), f(n + 1), invs(n + 1), inv(n + 1);
    f[0] = 1;
    for (int i = 1; i <= n; i++)
    {
        scanf("%lld", &a[i]);
        f[i] = (f[i - 1] * a[i]) % p;
    }
    invs[n] = qmi(f[n], p - 2, p);
    ll ans = 0;
    ll res = 1;
    for (int i = n; i; i--)
    {
        inv[i] = (f[i - 1] * invs[i]) % p;
        invs[i - 1] = (invs[i] * a[i]) % p;
    }
    for (int i = n; i; i--)
    {
        ans = (ans + inv[i] * res) % p;
        res = (res * mod) % p;
    }
    cout << ans;
}

int main()
{
    int t = 1;
    while (t--)
    {
        solve();
    }
    return 0;
}
posted @ 2023-09-30 15:34  value0  阅读(975)  评论(0)    收藏  举报