乘法逆元介绍
如果
a
x
≡
1
(
m
o
d
p
)
a x \equiv 1 (mod p)
ax≡1(modp),且 GCD(a,p) = 1,则称 a 的模p意义下的乘法逆元为 x。
同时 a 也是 x 模 p 意义下的乘法逆元。a 的逆元记为
a
−
1
a^{-1}
a−1
乘法逆元的作用
首先,除法取模不像乘法取模那样 “自由”:(a / b)%p
≠
\neq
= (a%p / b%p)%p,所以如果你想求一个大分式取模,即 a 和 b 都是结果很大的表达式,计算会十分复杂。所以引入了乘法逆元,将除法取模转换为乘法取模来简化计算。
结论:(a/b)%p = (a
×
b
−
1
\times b^{-1}
×b−1) % p。
当然 b 和 p 要互质,这样才有逆元。
求乘法逆元的主要方法
方法一:扩展欧几里得算法。
如果 ax
≡
\equiv
≡ 1 (mod p),那么 ax % p = 1。那么存在 x,y 使得 ax + py = 1,直接用扩展欧几里得算法求出 x 和 y,x 就是 a 在模 p 意义下的逆元。
代码:
ll exgcd(ll a, ll b, ll &x, ll &y)
{
if(b==0)
{
x = 1; y = 0;
return a;
}
ll r = exgcd(b, a%b, x, y);
ll temp = y;
y = x-(a/b)*y;
x = temp;
return r;
}
方法二:费马小定理+快速幂。
费马小定理内容:如果 p 是一个质数,而整数 a 不是 p 的倍数,则有
a
p
−
1
≡
1
(
m
o
d
p
)
a^{p-1} \equiv 1(modp)
ap−1≡1(modp)。
a
p
−
1
≡
1
(
m
o
d
p
)
a^{p-1} \equiv 1(modp)
ap−1≡1(modp) 很容易得到
a
×
a
p
−
2
≡
1
(
m
o
d
p
)
a\times a^{p-2} \equiv 1(modp)
a×ap−2≡1(modp),当 a,p 互质时,
a
p
−
2
a^{p-2}
ap−2 就是 a 在模p 意义下的逆元。但前提是 p 是一个质数,这样才有费马小定理成立。然后用快速幂算法求
a
p
−
2
a^{p-2}
ap−2 即可。
代码略。
线性求1~n的逆元
求出1,2,3…n 中每个数字关于 p 的逆元,如果对每个数字使用上面的两种方法,速度就很慢了,但我们可以推导出逆元的线性递推式。
首先有:
1
−
1
≡
1
1^{-1} \equiv 1
1−1≡1 (mod p) ,
然后设:
p
=
k
i
+
j
p = ki+j
p=ki+j
(
j
<
i
,
1
<
i
<
p
)
(j<i,1<i<p)
(j<i,1<i<p),再放到 mod p 意义下就会得到:
k
i
+
j
≡
0
ki+j \equiv 0
ki+j≡0 (mod p),
两边同时乘
i
−
1
,
j
−
1
i^{-1},j^{-1}
i−1,j−1,
k
j
−
1
+
i
−
1
≡
0
kj^{-1}+i^{-1} \equiv 0
kj−1+i−1≡0 (mod p),
i
−
1
≡
−
k
j
−
1
i^{-1} \equiv -kj^{-1}
i−1≡−kj−1 (mod p),
i
−
1
≡
−
(
p
i
)
(
p
m
o
d
i
)
−
1
i^{-1} \equiv -(\frac {p}{i})(p mod i^{})^{-1}
i−1≡−(ip)(pmodi)−1
然后我们就可以推出逆元了。
代码:
inv[1] = 1;
for (int i = 2; i <= n; ++i)
inv[i] = (long long)(p - p / i) * inv[p % i] % p;
线性求任意n个数的逆元
上面的方法只能求 1~n 的逆元,如果需要求任意给定 n 个数
(
1
≤
a
i
<
p
)
(1 \leq a_i < p)
(1≤ai<p)的逆元,就需要用下面的方法。
首先计算
n
n
n 个数的前缀积,记为
s
i
s_i
si,然后使用快速幂或扩展欧几里得算法计算
s
n
s_n
sn 的逆元,记为
s
v
n
sv_n
svn。
因为
s
v
n
sv_n
svn 是
n
n
n 个数的积的逆元,所以当我们把它乘上
a
n
a_n
an 时,就会和
a
n
a_n
an 的逆元抵消,于是就得到了
a
1
a_1
a1 到
a
n
−
1
a_{n-1}
an−1 的积逆元,记为
s
v
n
−
1
sv_{n-1}
svn−1。
同理我们可以依次计算出所有的
s
v
i
sv_i
svi,于是
a
i
−
1
a_i^{-1}
ai−1 就可以用
s
i
−
1
×
s
v
i
s_{i-1} \times sv_i
si−1×svi 求得。
所以我们就在
O
(
n
+
l
o
g
p
)
O(n + logp)
O(n+logp) 的时间内计算出了
n
n
n 个数的逆元。
代码:
s[0] = 1;
for (int i = 1; i <= n; ++i) s[i] = s[i - 1] * a[i] % p;
sv[n] = qpow(s[n], p - 2);
// 当然这里也可以用 exgcd 来求逆元,视个人喜好而定.
for (int i = n; i >= 1; --i) sv[i - 1] = sv[i] * a[i] % p;
for (int i = 1; i <= n; ++i) inv[i] = sv[i] * s[i - 1] % p;
此部分来自 OI Wiki.
例题
求逆元模板: 洛谷P1082 同余方程.
逆元简单应用:洛谷P2613 【模板】有理数取余.
线性逆元模板:洛谷P3811 【模板】乘法逆元.
任意N个数字逆元:洛谷P5431 【模板】乘法逆元2.