逆元是什么以及为什么要使用逆元
逆元的作用:在模意义下做乘法的逆运算。
在实数运算中,除以一个数,等于乘上这个数的倒数。而现在在模意义下,逆元就充当了“倒数”的角色,模意义下乘上逆元就相当于除以了一个数。
逆元定义:如果一个线性同余方程
a
x
≡
1
(
m
o
d
b
)
ax \equiv 1 \pmod b
ax≡1(modb),则
x
x
x 称为
a
m
o
d
b
a \bmod b
amodb 的逆元,记作
a
−
1
a^{-1}
a−1。所以求逆元实际上就是求如下方程的解:
a
x
≡
1
(
m
o
d
p
)
ax \equiv 1 \pmod p
ax≡1(modp)
费马小定理求逆元
费马小定理
若 p p p 为素数,且 gcd ( a , p ) = 1 \gcd(a, p) = 1 gcd(a,p)=1,即 a , p a, p a,p 互质,那么 a p − 1 ≡ 1 ( m o d p ) a^{p-1} \equiv 1 \pmod p ap−1≡1(modp)。
如何用于求逆元
当 p p p 是质数时,
∵ a p − 1 ≡ 1 ( m o d p ) \because a^{p-1} \equiv 1 \pmod p ∵ap−1≡1(modp)
∴ a ⋅ a p − 2 ≡ 1 ( m o d p ) \therefore a \cdot a^{p-2} \equiv 1 \pmod p ∴a⋅ap−2≡1(modp)
∴ x = a p − 2 m o d p \therefore x = a^{p-2} \bmod p ∴x=ap−2modp
用个快速幂就结束了。
注意限制条件: p p p 必须是质数。
但是大多数情况下OI题目中的模数都是质数,所以这一方法很常用。
时间复杂度 O ( log p ) O(\log p) O(logp)。
欧拉定理求逆元
欧拉函数
欧拉函数 φ ( x ) \varphi(x) φ(x) 的定义:小于等于 x x x 的数中与 x x x 互质的数的个数。
如何求欧拉函数:
φ
(
x
)
=
(
∏
i
=
1
k
p
i
−
1
p
i
)
x
\varphi(x) = (\prod_{i=1}^k \dfrac{p_i-1}{p_i}) x
φ(x)=(i=1∏kpipi−1)x
其中
k
k
k 是
x
x
x 中的质因子个数,
p
i
p_i
pi 是
x
x
x 的一个质因子。注意,这里忽略质因数分解中的次数,如
20
=
2
2
×
5
20 = 2^2 \times 5
20=22×5
但是我们计算时只考虑 2 2 2 和 5 5 5 这两个质因子。
欧拉定理
若 gcd ( a , p ) = 1 \gcd(a, p) = 1 gcd(a,p)=1,那么 a φ ( p ) ≡ 1 ( m o d p ) a^{\varphi(p)} \equiv 1 \pmod p aφ(p)≡1(modp)。
如何用于求逆元
∵ a φ ( p ) ≡ 1 ( m o d p ) \because a^{\varphi(p)} \equiv 1 \pmod p ∵aφ(p)≡1(modp)
∴ a ⋅ a φ ( p ) − 1 ≡ 1 ( m o d p ) \therefore a \cdot a^{\varphi(p) - 1} \equiv 1 \pmod p ∴a⋅aφ(p)−1≡1(modp)
∴ x = a φ ( p ) − 1 m o d p \therefore x = a^{\varphi(p)-1} \bmod p ∴x=aφ(p)−1modp
质因数分解求 φ ( p ) \varphi(p) φ(p) 加上一个快速幂即可。
最坏时间复杂度 O ( p + log p ) O(\sqrt p + \log p) O(p+logp)。
和费马小定理的关系
有没有发现二者长得很像?
其实费马小定理是欧拉定理的一种特殊情况:
当
p
p
p 为质数时,易得
φ
(
p
)
=
p
−
1
\varphi(p) = p - 1
φ(p)=p−1,此时欧拉定理就成了费马小定理。
例题代码
#include<iostream>
using namespace std;
long long a, b;
long long phi(long long x)
{
long long res = x;
for(long long i = 2; i * i <= x; i++)
if(x % i == 0)
{
res = res / i * (i - 1);
while(x % i == 0)
x /= i;
}
if(x > 1)
res = res / x * (x - 1);
return res;
}
long long qpow(long long a, long long b, long long c)
{
long long res = 1;
while(b)
{
if(b & 1)
res = res * a % c;
b >>= 1;
a = a * a % c;
}
return res;
}
int main()
{
cin >> a >> b;
cout << qpow(a, phi(b) - 1, b) << endl;
return 0;
}
扩展欧几里得算法求逆元
裴蜀定理
对于任意整数 a , b a,b a,b,若 a , b a,b a,b 不全为 0 0 0,则存在整数 x , y x,y x,y,满足 a x + b y = gcd ( a , b ) ax + by = \gcd(a,b) ax+by=gcd(a,b)。
扩展欧几里得算法(简称扩欧,英文名称 exgcd)就是用来求一组满足条件的 x , y x,y x,y 的。
扩展欧几里得算法
当 b = 0 b=0 b=0 时,有 a x = gcd ( a , 0 ) = a ax = \gcd(a,0) = a ax=gcd(a,0)=a,易得 x = 1 , y = 0 x=1,y=0 x=1,y=0。
我们知道 gcd ( a , b ) = gcd ( b , a m o d b ) \gcd(a,b) = \gcd(b, a \bmod b) gcd(a,b)=gcd(b,amodb),那么我们在知道
b x ′ + ( a m o d b ) y ′ = gcd ( b , a m o d b ) bx' + (a \bmod b) y' = \gcd(b, a \bmod b) bx′+(amodb)y′=gcd(b,amodb)
的一组特解 x ′ , y ′ x',y' x′,y′ 的情况下如何推出 x , y x,y x,y 呢?
a = ⌊ a b ⌋ ⋅ b + a m o d b a = \left\lfloor\dfrac{a}{b}\right\rfloor\cdot b + a \bmod b a=⌊ba⌋⋅b+amodb
将其代入 a x + b y = gcd ( a , b ) ax + by = \gcd(a,b) ax+by=gcd(a,b) 中整理可得
b ( ⌊ a b ⌋ ⋅ x + y ) + ( a m o d b ) x = gcd ( a , b ) b(\left\lfloor\dfrac{a}{b}\right\rfloor\cdot x + y) + (a \bmod b)x = \gcd(a,b) b(⌊ba⌋⋅x+y)+(amodb)x=gcd(a,b)
所以
{ ⌊ a b ⌋ ⋅ x + y = x ′ x = y ′ \begin{cases} \left\lfloor\dfrac{a}{b}\right\rfloor\cdot x + y=x'\\ x=y' \end{cases} {⌊ba⌋⋅x+y=x′x=y′
所以
{ x = y ′ y = x ′ − ⌊ a b ⌋ ⋅ x \begin{cases} x=y'\\ y=x'-\left\lfloor\dfrac{a}{b}\right\rfloor\cdot x \end{cases} {x=y′y=x′−⌊ba⌋⋅x
如何用于求逆元
求方程 a x ≡ 1 ( m o d p ) ax \equiv 1 \pmod p ax≡1(modp) 的解本质上就是求二元一次不定方程
a x + p y = 1 ax + py = 1 ax+py=1
的解中的整数解 x x x。使用扩展欧几里得算法即可求出 x x x 的一个特解。
而我们发现,
a ( x + k p ) + p ( y − k a ) = 1 a(x + kp) + p(y -ka)=1 a(x+kp)+p(y−ka)=1
所以我们可以不断调整 x x x 得到通解。
也可看出,逆元存在的充分必要条件是 a a a 与 p p p 互质。
时间复杂度 O ( log ( a + p ) ) O(\log (a+p)) O(log(a+p))。
例题代码
注意要求 x x x 的最小正整数解。
#include<iostream>
using namespace std;
int exgcd(int a, int b, int &x, int &y)
{
if(b == 0)
{
x = 1;
y = 0;
return a;
}
int d = exgcd(b, a % b, y, x);
y -= a / b * x;
return d;
}
int main()
{
int a, b, x, y;
cin >> a >> b;
exgcd(a, b, x, y);
x %= b;
if(x < 0)
x += b; //注意x可能为负数
cout << x << endl;
return 0;
}
线性求连续正整数的逆元
首先易得 1 − 1 = 1 1^{-1} = 1 1−1=1。
然后因为
p
=
⌊
p
i
⌋
⋅
i
+
p
m
o
d
i
p = \left\lfloor\dfrac{p}{i}\right\rfloor\cdot i + p \bmod i
p=⌊ip⌋⋅i+pmodi,令
k
=
⌊
p
i
⌋
k=\left\lfloor\dfrac{p}{i}\right\rfloor
k=⌊ip⌋,
j
=
p
m
o
d
i
j=p \bmod i
j=pmodi,放到模意义下就会得到
k
i
+
j
≡
0
(
m
o
d
p
)
ki + j \equiv 0 \pmod p
ki+j≡0(modp)
两边同时乘
i
−
1
j
−
1
i^{-1}j^{-1}
i−1j−1 得到
k
j
−
1
+
i
−
1
≡
0
(
m
o
d
p
)
kj^{-1}+i^{-1} \equiv 0 \pmod p
kj−1+i−1≡0(modp)
所以有
i
−
1
≡
−
k
j
−
1
(
m
o
d
p
)
i^{-1} \equiv -kj^{-1} \pmod p
i−1≡−kj−1(modp)
即
i
−
1
≡
−
⌊
p
i
⌋
⋅
(
p
m
o
d
i
)
−
1
(
m
o
d
p
)
i^{-1} \equiv -\left\lfloor\dfrac{p}{i}\right\rfloor \cdot (p \bmod i)^{-1} \pmod p
i−1≡−⌊ip⌋⋅(pmodi)−1(modp)
代码实现(来自OI-wiki):
inv[1] = 1;
for(int i = 2; i <= n; i++)
inv[i] = (p - p / i) * inv[p % i] % p;
使用 p - p / i
来防止出现负数,且对结果不会产生影响。
线性求阶乘逆元
∵ n ! = ( n − 1 ) ! × n \because n!=(n-1)! \times n ∵n!=(n−1)!×n
∴ ( n ! ) − 1 ≡ [ ( n − 1 ) ! ] − 1 × n − 1 ( m o d p ) \therefore (n!)^{-1} \equiv [(n-1)!]^{-1} \times n^{-1} \pmod p ∴(n!)−1≡[(n−1)!]−1×n−1(modp)
∴ ( n ! ) − 1 × n ≡ [ ( n − 1 ) ! ] − 1 ( m o d p ) \therefore (n!)^{-1} \times n \equiv [(n-1)!]^{-1} \pmod p ∴(n!)−1×n≡[(n−1)!]−1(modp)
最终我们得到
[
(
n
−
1
)
!
]
−
1
≡
(
n
!
)
−
1
×
n
(
m
o
d
p
)
[(n-1)!]^{-1} \equiv (n!)^{-1} \times n \pmod p
[(n−1)!]−1≡(n!)−1×n(modp)
其实不看推导过程这个也很好理解。而 ( n ! ) − 1 (n!)^{-1} (n!)−1 我们可以用扩欧、欧拉定理或者费马小定理算一下就好了。
例题:
- SDOI2016 排列计数(这题需要其它的知识,求解的过程中要用到此方法)
- NOI Online #2 入门组 建设城市(这题也需要其它的知识)