乘法逆元
前置知识:扩展欧几里得算法
定义:
乘法逆元,是指数学领域群G中任意一个元素a,都在G中有唯一的逆元a‘,具有性质a×a’=a’×a=e,其中e为该群的单位元
(以上内容来自百度)
我们要考虑的是模 m 乘法逆元:若整数 b,m 互质,并且 b ∣ a b \mid a b∣a,则存在一个整数 x,使得 a x ≡ a / b ( m o d p ) ax \equiv a/b \pmod p ax≡a/b(modp)。我们就称 x 为 b 的模 m 乘法逆元。记做 b − 1 b^{-1} b−1
求解乘法逆元
因为:
a
/
b
≡
a
b
−
1
≡
a
×
b
/
b
×
b
−
1
(
m
o
d
m
)
a/b \equiv ab^{-1} \equiv a \times b/b \times b^{-1} \pmod m
a/b≡ab−1≡a×b/b×b−1(modm)
所以:
b
×
b
−
1
≡
1
(
m
o
d
m
)
b\times b^{-1} \equiv 1 \pmod m
b×b−1≡1(modm)
如果 m 是质数(后文用 p 代替),并且 b < p,根据欧拉定理(
b
ϕ
(
p
)
≡
1
(
m
o
d
p
)
b^{\phi(p)} \equiv 1 \pmod p
bϕ(p)≡1(modp))我们知道
b
p
−
1
≡
1
(
m
o
d
p
)
b^{p-1} \equiv 1 \pmod p
bp−1≡1(modp),也就是说
b
×
b
p
−
2
≡
1
(
m
o
d
p
)
b\times b^{p-2} \equiv 1 \pmod p
b×bp−2≡1(modp)。因此,当模数 p 为质数时,
b
p
−
2
b^{p-2}
bp−2 就是 b 的模 p 乘法逆元。
但是对于更一般的情况就只能通过求解一次同余方程来求得乘法逆元了。下面我们来说线性同余方程的解法:
对于这个方程:
a
x
≡
b
(
m
o
d
m
)
ax \equiv b \pmod m
ax≡b(modm),它等价于
m
∣
(
a
x
−
b
)
m \mid (ax-b)
m∣(ax−b)。我们令 ax - b = -ym。则有 ax + my = b。根据在扩展欧几里得算法里面讲过的
B
e
ˊ
z
o
u
t
B\acute{e}zout
Beˊzout 定理及其推论,当且仅当
g
c
d
(
a
,
m
)
∣
b
gcd(a, m) \mid b
gcd(a,m)∣b 时,方程有解。在有解时,我们先用 exgcd 求出ax + my = gcd(a, m) 的一组特解
x
0
,
y
0
x_0, y_0
x0,y0。然后
x
=
x
0
b
g
c
d
(
a
,
m
)
x = x_0 \frac{b}{gcd(a, m)}
x=x0gcd(a,m)b就是原方程的一个解了。
在求解乘法逆元时就是求解方程:
a
x
≡
1
(
m
o
d
b
)
ax \equiv 1\pmod b
ax≡1(modb)时,先改写方程为
a
x
+
b
y
=
1
ax + by = 1
ax+by=1,所以我们知道方程当且仅当 a, b 互质时有解。先用 exgcd 得求出方程的一组特解
x
0
,
y
0
x_0, y_0
x0,y0。则
x
0
x_0
x0 就是原方程的一个解。
通解也很好想出来就是模 b 与
x
0
x_0
x0 同余的所有整数。所以我们如果想要 x 的最小整数解,我们就可以利用取模运算把
x
0
x_0
x0 移动到 [1, b) 的区间内:
x
0
=
(
(
x
0
m
o
d
b
)
+
b
)
m
o
d
b
x_0 = ((x_0 \;mod \; b) + b) \; mod \;b
x0=((x0modb)+b)modb
#include<bits/stdc++.h>
using namespace std;
int a = 0, b = 0;
1. List item
int exgcd(int a, int b, int &x, int &y){ // ax + by = gcd(a, b) --> (x, y)
if(b == 0){
x = 1; y = 0;
return a;
}
int d = exgcd(b, a % b, x, y);
int z = x;
x = y; y = z - (a / b) * y;
return d;
}
int inver(int a, int b){ // ax ≡ 1 (mod b) --> x
int x = 0; int y = 0;
exgcd(a, b, x, y);
return (x % b + b) % b;
}
int main(){
scanf("%d%d", &a, &b);
printf("%d\n", inver(a, b));
return 0;
}
另一种方法求逆元–递推
扩展欧几里得常用于求解单个数的逆元,而如果我们需要一堆数的逆元的话,我们一般就是用递推的方法进行求解。推导过程如下:
设:
i
k
+
r
=
p
→
i
k
+
r
≡
0
(
m
o
d
p
)
ik + r = p \rightarrow ik + r \equiv 0 \pmod p
ik+r=p→ik+r≡0(modp)
两边同时乘上
i
−
1
r
−
1
i^{-1}r^{-1}
i−1r−1 得到:
i
k
×
i
−
1
r
−
1
+
r
×
i
−
1
r
−
1
≡
0
(
m
o
d
p
)
→
k
r
−
1
+
i
−
1
(
m
o
d
p
)
ik\times i^{-1}r^{-1} + r\times i^{-1}r^{-1} \equiv 0 \pmod p \rightarrow kr^{-1} + i^{-1} \pmod p
ik×i−1r−1+r×i−1r−1≡0(modp)→kr−1+i−1(modp)
所以:
i
−
1
≡
−
k
r
−
1
(
m
o
d
p
)
i^{-1} \equiv -kr^{-1} \pmod p
i−1≡−kr−1(modp)
因为我们设的是
i
k
+
r
=
p
ik + r = p
ik+r=p,所以
r
∈
(
0
,
i
)
r \in (0, i)
r∈(0,i),r < i。所以在递推的过程中,
r
−
1
r^{-1}
r−1 肯定在之前就被求出来了。所以我们就可以由此确定
i
−
1
i^{-1}
i−1。下面证明递推式:
因为
i
k
+
r
=
p
ik + r = p
ik+r=p 所以
k
=
⌊
p
i
⌋
,
r
=
p
m
o
d
i
k = \lfloor\frac{p}{i}\rfloor,r = p\mod i
k=⌊ip⌋,r=pmodi,所以:
i
−
1
≡
−
⌊
p
i
⌋
×
(
p
m
o
d
i
)
−
1
(
m
o
d
p
)
i^{-1} \equiv - \lfloor \frac{p}{i} \rfloor \times (p \; mod \; i)^{-1} \pmod p
i−1≡−⌊ip⌋×(pmodi)−1(modp)
然后我们就可以得到递推式:
i
−
1
=
−
⌊
p
i
⌋
×
(
p
m
o
d
i
)
i^{-1} = - \lfloor \frac{p}{i} \rfloor \times (p \; mod \; i)
i−1=−⌊ip⌋×(pmodi)
如果我们要得到最小整数解,我们可以在乘法的第一项加上一个 p,就是这样:
i
−
1
=
(
p
−
⌊
p
i
⌋
)
×
(
p
m
o
d
i
)
i^{-1} = (p - \lfloor \frac{p}{i} \rfloor) \times (p \; mod \; i)
i−1=(p−⌊ip⌋)×(pmodi)
代码如下:
#include<bits/stdc++.h>
using namespace std;
#define MAXN 100100
int n = 0; int p = 0;
int inv[MAXN] = { 0 };
int main(){
scanf("%d%d", &n, &p);
inv[1] = 1;
for(int i = 2; i <= n; i++){
inv[i] = (p - p / i) * inv[p % i] % p;
printf("%d ", inv[i]);
}
return 0;
}
参考书籍:《算法竞赛进阶指南》