【数论】乘法逆元
Definition
对于一个数 (x) 和一个模数 (p),若存在一个数字 (y),满足
则称 (y) 是 (x) 在模 (p) 意义下的逆元,记做 (x^{-1}~equiv y pmod p)。
一个数字逆元在模意义下的运算中可以完全取代该数字的倒数。例如 (frac{x}{y}~equiv x imes y^{-1} pmod p),其中 (y^{-1}) 代表 (y) 的逆元。
Algorithm
Lemma
首先需要指出的是,一个数 (x) 在模 (p) 意义下存在逆元,当且仅当 (x) 与 (p) 互质。
Proof
这里只证明当 (x) 与 (p) 不互质时不存在逆元。对于逆元的存在性,由于下面的部分给出了逆元的构造算法,这就已经证明了在互质时逆元是存在的。
反证法,设对于任意的 (x in Z^+),存在 (x^{-1} in Z^+)
且
根据同余的定义,((1)) 可以写成:
其中 (k) 是一个非负整数。
将 ((3)) 的等号两侧同时除以 ((2)) 中的 (d):
整理得到
因为 (d = gcd(x, p)),所以 (d) 一定是 (x) 和 (p) 的因数。所以
(frac{x}{d}) 和 (frac{p}{d}) 都是整数,进而 (frac{p}{d} imes k) 是整数,(frac{x}{d} imes x^{-1}) 是整数。
而因为 (d eq 1),所以 (frac{1}{d}) 一定不是整数,因此 (frac{p}{d} imes k + frac{1}{d}) 不是整数。
于是等号左侧是整数,等号右侧不是整数,左侧一定不等于右侧,产生矛盾。这就矛盾证明了 (x) 在模 (p) 意义下存在逆元仅当 (x) 与 (p) 互质。
以下介绍求逆元的算法:
求单个数字的逆元
Algorithm 1
显然可以转化成方程
(x imes x^-1 = 1 + kp)
令 (y = -k),移项得到
注意到这个式子就是扩展欧几里得算法所求的式子
只不过 (x) 作为一个常数,是欧式式子里的 (a),同理 (p) 是欧式式子里的 (b)。使用扩展欧几里得算法求解上面这个式子即可。时间复杂度 (O(log x))。
Algorithm 2
根据欧拉定理
(x^{phi(p)} equiv 1 pmod p)
其中 (phi) 为欧拉函数,(phi(p)) 表示小于 (p) 的正整数中与 (p) 互质的数的个数。
等式两侧同乘 (x^{-1}) 可以得到
显然当 (p) 是一个质数时,(phi(p) = p - 1),这时可以 (O(1)) 算出 (phi(p) - 1) 的值,即可用快速幂 (O(log x)) 求出 (x) 的逆元。这个算法好写好记,常数也较小。一般当 (p) 为 int
范围内的质数时选择此算法。当 (p) 不在 int
范围内时,由于快速幂时需要两个 long long
相乘,会爆精度。
有关欧拉定理的证明可以看这里
求 (n) 以内所有正整数模 (p) 的逆元
显然,由于 (n) 以内所有正整数都有在模 (p) 意义下的逆元,所以 (p) 和 (n) 以内的所有数互质。
结论:设 (inv_i) 为 (i) 的逆元,则有递推式
边界条件为
Proof
首先 (inv_1 = 1) 显然成立。
对于 (i > 1),写出 (p) 除以 (i) 的带余除法表达式:
其中 (r in [0, i - 1])
等式两侧对 (p) 取余数,有
移项得到
两侧同乘 (i^{-1} imes r^{-1}),整理得到
由于 (k = leftlfloorfrac{p}{i} ight floor),(r = p mod i),所以原式得证。
又因为 (r < i),所以在计算 (inv_i) 时,(inv_r) 已经被计算完成,所以上述递推可以完成。
证毕。
这样做的时间复杂度显然是 (O(n))
求 (n!) 的逆元
因为 ((n!)^{-1} equiv frac{1}{n!} equiv prod_{i = 1}^n n^{-1}),所以线性筛出 (n) 以内所有数字的逆元时,可以顺便求出 (n!) 的逆元。时间复杂度 (O(n))
Code
Ex_Gcd
#include <iostream>
typedef long long int ll;
ll x, p;
void Ex_gcd(const ll a, const ll b, ll &X, ll &Y);
int main() {
std::cin >> x >> p;
ll a, b;
Ex_gcd(x, p, a, b);
std::cout << (a % p + p) % p << std::endl;
return 0;
}
void Ex_gcd(const ll a, const ll b, ll &X, ll &Y) {
if (b == 0) {
X = 1; Y = 0;
} else {
Ex_gcd(b, a % b, Y, X);
Y -= a / b * X;
}
}
欧拉定理
#include <iostream>
typedef long long int ll;
ll X, p;
ll mpow(ll x, ll y);
int main() {
std::cin >> X >> p;
std::cout << mpow(X, p - 2) << std::endl;
return 0;
}
ll mpow(ll x, ll y) {
ll _ret = 1;
while (y) {
if (y & 1) (_ret *= x) %= p;
y >>= 1;
(x *= x) %= p;
}
return _ret;
}
线性求逆元
这里的 factinv
即为阶乘逆元。
#include <cstdio>
const int maxn = 3000005;
int n, p;
int inv[maxn], factinv[maxn];
int main() {
scanf("%d%d", &n, &p);
factinv[1] = inv[1] = 1;
printf("%d
", 1);
for (int i = 2; i <= n; ++i) {
inv[i] = 1ll * (p - p / i) * inv[p % i] % p;
printf("%d
", inv[i]);
factinv[i] = 1ll * factinv[i - 1] * inv[i] % p;
}
return 0;
}