只不过 x 作为一个常数,是欧式式子里的 a,同理 p 是欧式式子里的 b。使用扩展欧几里得算法求解上面这个式子即可。时间复杂度 O(\log x)。
Algorithm 2
根据欧拉定理
x^{\phi(p)} \equiv 1 \pmod p
其中 \phi 为欧拉函数,\phi(p) 表示小于 p 的正整数中与 p 互质的数的个数。
等式两侧同乘 x^{-1} 可以得到
x^{\phi(p) - 1} \equiv x^{-1} \pmod p
显然当 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 的逆元,则有递推式
inv_i \equiv -\left\lfloor\frac{p}{i}\right\rfloor \times inv_{p \bmod i} \pmod p
边界条件为
inv_1 = 1
Proof
首先 inv_1 = 1 显然成立。
对于 i > 1,写出 p 除以 i 的带余除法表达式:
p = ki + r
其中 r \in [0, i - 1]
等式两侧对 p 取余数,有
0 \equiv ki + r \pmod p
移项得到
r \equiv -ki \pmod p
两侧同乘 i^{-1} \times r^{-1},整理得到
i^{-1} \equiv -kr^{-1} \pmod p
由于 k = \left\lfloor\frac{p}{i}\right\rfloor,r = p \bmod i,所以原式得证。
#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\n", 1);
for (int i = 2; i <= n; ++i) {
inv[i] = 1ll * (p - p / i) * inv[p % i] % p;
printf("%d\n", inv[i]);
factinv[i] = 1ll * factinv[i - 1] * inv[i] % p;
}
return 0;
}