【數學】逆元
一、逆元的意義
在一道題中,如果既有除法又有取餘,那麼是不能直接除的:
\[\dfrac{a}{b}\bmod p\ne \dfrac{a\bmod p}{b\bmod p} \]這時候逆元就登場了.
眾所周知,\(a\cdot a^{-1}=1\),類似地定義 \(a\) 在模 \(p\) 意義下的逆元為使得
\[a\cdot x\equiv 1\pmod p \]成立的 \(x\).
注意到當 \(\gcd(a,p)>1\) 時上式不可能成立,所以只有 \(a\) 與 \(p\) 互質時才有逆元.
那麼在模 \(p\) 意義下,\(x=a^{-1}\),\(x\) 記作 \(inv(a)\)
考慮回剛剛的問題:
\[\dfrac{a}{b}\bmod p=(a\cdot b^{-1})\bmod p \]所以求出 \(inv(b)\),答案就是 \((a\cdot inv(b))\bmod p\).
二、求單個數的逆元
臭模數
用快讀 \(+\) 取餘讀入.
首先 \(19260817\) 是個質數,所以當且僅當 \(19260817\mid b\) 時 \(b\) 沒有逆元,即無解.
以下討論都在 \(\gcd(a,p)=1\) 的前提下.
1. 費馬小定理
此情況僅適用於 \(p\) 為質數的情況.
由費馬小定理知,當 \(p\) 為質數且 \(\gcd(a,p)=1\)
兩邊同除 \(a\) 得
\[a^{p-2}\equiv a^{-1}\pmod p \]所以 \(inv(a)\) 其實就是 \(a^{p-2}\).
本題的答案就是 \(a\cdot b^{p-2}\).
\(\text{Code}\)
#include <iostream> #include <cstdio> #define ll long long using namespace std; const int MOD = 19260817; int read() { int x = 0; char c = getchar(); while (c < '0' || c > '9') { c = getchar(); } while (c >= '0' && c <= '9') { x = ((x << 3) + (x << 1) + c - '0') % MOD; c = getchar(); } return x; } ll ksm(int a, int b) { ll base = a, ans = 1; while (b) { if (b & 1) { ans = (ans * base) % MOD; } base = (base * base) % MOD; b >>= 1; } return ans; } int main() { int a = read(), b = read(); if (!b) { puts("Angry!"); return 0; } printf("%lld\n", a * ksm(b, MOD - 2) % MOD); return 0; }
2. \(\rm EXGCD\)
當 \(p\) 不為質數時 \(\rm EXGCD\) 也能求逆元.
要求出
\[a\cdot x\equiv 1\pmod p \]相當於求出
\[ax+py=1 \]的整數解.
注意:用 \(\rm EXGCD\) 求出來的可能是負數,要轉成正數.
\(\text{Code}\)
#include <iostream>
#include <cstdio>
#define ll long long
using namespace std;
const int MOD = 19260817;
int read()
{
int x = 0;
char c = getchar();
while (c < '0' || c > '9')
{
c = getchar();
}
while (c >= '0' && c <= '9')
{
x = ((x << 3) + (x << 1) + c - '0') % MOD;
c = getchar();
}
return x;
}
ll x, y;
void exgcd(int a, int b)
{
if (!b)
{
x = 1, y = 0;
return;
}
exgcd(b, a % b);
ll tmp = x;
x = y;
y = tmp - a / b * y;
}
int main()
{
int a = read(), b = read();
if (!b)
{
puts("Angry!");
return 0;
}
exgcd(b, MOD);
x = (x % MOD + MOD) % MOD;
printf("%lld\n", a * x % MOD);
return 0;
}
以上兩種演算法的時間複雜度均為 \(\mathcal{O}(\log n)\).
三、線性求 \(1\sim n\) 的逆元
單個求時間為 \(\mathcal{O}(n\log n)\),會炸.
1. 遞推
首先 \(inv(1)=1\).
然後 \(p\) 可以表示成 \(iq+r\).
\[p\equiv0\pmod p\\ iq+r\equiv0\pmod p\\ r\equiv -iq\pmod p\\ r\cdot(i^{-1}r^{-1})\equiv-iq\cdot(i^{-1}r^{-1})\pmod p\\ i^{-1}\equiv-q\cdot r^{-1}\pmod p\\ i^{-1}\equiv-q\cdot(p\bmod i)^{-1}\pmod p\\ inv(i)\equiv-\left\lfloor\frac{p}{i}\right\rfloor\cdot inv(p\bmod i)\pmod p \]然後就可以線性推了.
注意這樣算出來會有負數,要轉成正的.
\(\text{Code}\)
#include <iostream>
#include <cstdio>
#define int long long
using namespace std;
const int MAXN = 3e6 + 5;
int inv[MAXN];
signed main()
{
int n, p;
scanf("%lld%lld", &n, &p);
inv[1] = 1;
puts("1");
for (int i = 2; i <= n; i++)
{
inv[i] = (-(p / i) * inv[p % i] % p + p) % p;
printf("%lld\n", inv[i]);
}
return 0;
}
2. 階乘逆元
首先,在 \(\bmod\ p\) 意義下
\[inv((i+1)!)=((i+1)!)^{-1}\\ ((i+1)!)^{-1}\cdot(i+1)=(i!)^{-1}=inv(i!)\\ \therefore inv((i+1)!)\cdot(i+1)=inv(i!) \]所以先 \(\mathcal{O}(\log n)\) 算出 \(inv(n!)\),就可以 \(\mathcal{O}(n)\) 一直推到 \(inv(1!)\) 了.
然後有
\[\begin{aligned} inv(i)&=i^{-1}\\ &=(i!)^{-1}\cdot (i-1)!\\ &=inv(i!)\cdot (i-1)! \end{aligned} \]再用 \(\mathcal{O}(n)\) 算出 \(inv(1)\sim inv(n)\).
\(\text{Code}\)
#include <iostream>
#include <cstdio>
#define ll long long
using namespace std;
const int MAXN = 3e6 + 5;
int n, p;
ll jc[MAXN], inv[MAXN];
ll ksm(int a, int b)
{
ll base = a, ans = 1;
while (b)
{
if (b & 1)
{
ans = ans * base % p;
}
base = base * base % p;
b >>= 1;
}
return ans;
}
int main()
{
scanf("%d%d", &n, &p);
jc[0] = jc[1] = 1;
for (int i = 2; i <= n; i++)
{
jc[i] = jc[i - 1] * i % p;
}
inv[n] = ksm(jc[n], p - 2);
for (int i = n - 1; i >= 1; i--)
{
inv[i] = inv[i + 1] * (i + 1) % p;
}
for (int i = 1; i <= n; i++)
{
printf("%lld\n", inv[i] * jc[i - 1] % p);
}
return 0;
}
以上兩種演算法的時間複雜度均為 \(\mathcal{O}(n)\).