线性求逆元
线性求逆元
时间复杂度:\(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;
}