在数论中,对于模乘运算,除法是一个很“不好”的运算。
先来看模的性质:
- ( a + b ) % p = ( a % p + b % p ) % p (a+b)\%p=(a\%p+b\%p)\%p (a+b)%p=(a%p+b%p)%p;
- ( a − b ) % p = ( a % p − b % p ) % p (a-b)\%p=(a\%p-b\%p)\%p (a−b)%p=(a%p−b%p)%p;
-
(
a
∗
b
)
%
p
=
(
a
%
p
∗
b
%
p
)
%
p
(a*b)\%p=(a\%p * b\%p)\%p
(a∗b)%p=(a%p∗b%p)%p。
注意:除法运算不满足。
举个例子,假设给一个数 b b b,和一个数 a a a,满足 b ∣ a b|a b∣a,现在要求 a b % p \frac{a}{b}\%p ba%p的值,这个值是不等于 ( a % p ) ( b % p ) \frac{(a\%p)}{(b\%p)} (b%p)(a%p)的,直接做的话,难免会有些精度问题。所以要设法把运算转换为乘法,也即求一个 x x x,使得 a b ≡ a x ( m o d p ) \frac{a}{b}≡ax(modp) ba≡ax(modp)。我们把这个 x x x记作 b − 1 ( m o d p ) b^{-1}(modp) b−1(modp),称为 b b b模 p p p的乘法逆元。
要学习求逆元,需要先掌握快速幂、扩展欧几里得算法,可以参考我的这篇文章【简单数论】欧拉函数、快速幂、扩展欧几里得算法。
对于
p
p
p是否为质数,有两种求解方法。其中法一适用于
p
p
p是质数,而法二适用于
p
p
p是任何数。
1、快速幂求逆元
当要模的数 p p p是质数时,进行如下推导:
有
a
b
≡
a
x
(
m
o
d
p
)
\frac{a}{b}≡ax(modp)
ba≡ax(modp),要求
x
x
x;
两侧同乘
b
b
b,得
a
≡
b
a
x
(
m
o
d
p
)
a≡bax(modp)
a≡bax(modp);
消掉
a
a
a,得
b
x
≡
1
(
m
o
d
p
)
bx≡1(modp)
bx≡1(modp);
根据费马小定理,有
b
p
−
1
≡
1
(
m
o
d
p
)
b^{p-1}≡1(modp)
bp−1≡1(modp);
提出一个
b
b
b,得
b
⋅
b
p
−
2
≡
1
(
m
o
d
p
)
b·b^{p-2}≡1(modp)
b⋅bp−2≡1(modp);
对比可得:
x
=
b
p
−
2
x=b^{p-2}
x=bp−2;
当
b
b
b是
p
p
p的倍数时,
b
x
≡
1
(
m
o
d
p
)
bx≡1(modp)
bx≡1(modp)一定无法成立,所以无解。
因此,只要求 b p − 2 b^{p-2} bp−2就是答案了,这里直接使用快速幂即可。给出代码:
#include <iostream>
using namespace std;
typedef long long LL;
LL qmi(int a, int b, int p)
{
LL res = 1 % p;
while (b)
{
if (b & 1) res = res * a % p;
a = (LL)a * a % p;
b >>= 1;
}
return res;
}
int main()
{
//求解a模p的乘法逆元
int a, p;
scanf("%d%d", &a, &p);
if (a % p == 0) puts("impossible");
else printf("%d\n", qmi(a, p - 2, p));
return 0;
}
2、扩展欧几里得算法求逆元
当 p p p不是质数时,就不能用费马小定理了。这时候进行如下推导:
有
b
x
≡
1
(
m
o
d
p
)
bx≡1(modp)
bx≡1(modp),要求
x
x
x;
也即有
b
x
%
p
=
1
bx\%p=1
bx%p=1;
也即有
b
x
+
p
y
=
1
bx+py=1
bx+py=1;
此形式和扩展欧几里得算法相同,因此只要最大公约数为1,所求
x
x
x即为
b
−
1
b^{-1}
b−1。
通过这个,也得到了
b
b
b模
p
p
p的乘法逆元存在的条件:
b
b
b与
p
p
p互质。
给出代码:
#include <iostream>
using namespace std;
int exgcd(int a, int b, int &x, int &y)
{
if (!b)
{
x = 1, y = 0;
return a;
}
int d = exgcd(b, a % b, y, x);
y -= a / b * x;
return d;
}
int main()
{
int a, p;
cin >> a >> p;
int x, y;
//如果a和p不沪互质(也即最大公约数不为1),那么无解
//求出来的逆元是落在0~p-1之间的
if (exgcd(a, p, x, y) == 1) printf("%d\n", (x % p + p) % p);
else printf("impossible\n");
return 0;
}