简介
若有线性同余方程 a x ≡ 1 ( m o d p ) ax\equiv 1\left( mod~p\right) ax≡1(mod p),则称 x x x 为 a a a 在模 p p p 意义下的逆元,记作 a − 1 a^{-1} a−1
扩展欧几里得法
Idea
原方程
a
x
≡
1
(
m
o
d
p
)
ax\equiv 1\left( mod~p\right)
ax≡1(mod p) 可转化为
a
x
+
p
y
=
1
ax + py = 1
ax+py=1
然后就可以采用和解线性同余方程相同的方法,通过求解
a
x
+
p
y
=
g
c
d
(
a
,
p
)
ax + py = gcd(a,p)
ax+py=gcd(a,p) 来得到方程的一组解
根据裴蜀定理,我们发现方程有解的条件为
g
c
d
(
a
,
p
)
=
1
gcd(a,p) = 1
gcd(a,p)=1 ,
a
a
a 和
p
p
p 互质
所以得出结论:
a
a
a 在 模
p
p
p 意义下的乘法逆元存在当且仅当
a
a
a,
p
p
p互质
故题目往往给的模数为一大质数,从而保证了
[
1
,
p
−
1
]
[1, p-1]
[1,p−1] 中所有整数都有模
p
p
p 意义下的逆元
因此,我们只需要求出方程
a
x
+
p
y
=
g
c
d
(
a
,
p
)
ax + py = gcd(a,p)
ax+py=gcd(a,p) 的最小整数解
x
0
x_0
x0,同时由于
g
c
d
(
a
,
p
)
=
1
gcd(a,p) = 1
gcd(a,p)=1
x
0
x_0
x0即为
a
−
1
a^{-1}
a−1
时间复杂度: O ( log p ) O(\log p) O(logp)
Code
//b 即为 p
ll exgcd(ll a, ll b, ll &x, ll &y) {
if (b == 0) {
x = 1, y = 0;
return a;
}
int d = exgcd(b, a % b, x, y);
int t = x; x = y; y = t - (a / b) * y;
return d;
}
快速幂法
Idea
Note :使用这种方法需满足 p p p 为质数
费马小定理:若p为质数,且整数 a a a 满足 p ∣ a p~|~a p ∣ a ,则有 a b − 1 ≡ 1 ( m o d p ) a^{b-1} \equiv 1~(mod ~ p) ab−1≡1 (mod p)
原方程转化为
a
p
−
2
≡
a
−
1
(
m
o
d
p
)
a^{p-2} \equiv a^{-1}~(mod ~ p)
ap−2≡a−1 (mod p)
所以只需要求
a
p
−
2
m
o
d
p
a^{p-2} mod~p
ap−2mod p 即可,可以用快速幂来求
时间复杂度: O ( log p ) O(\log p) O(logp)
Code
//qpow(a,p-2)
inline int qpow(ll a,int n){
int res = 1;
a = (a % mod + mod) % mod;
while(n){
if(n & 1) res = (res * a) % mod;
a = (a * a) % mod;
n >>= 1;
}
return res;
}
线性求[1,n]逆元
Note :使用这种方法需满足 p p p 为质数
Idea
若要求
[
1
,
n
]
[1,n]
[1,n] 每个数的逆元,虽然可以使用上面介绍的两种求单个数逆元的方法
但复杂度来到了
O
(
n
log
p
)
O(n\log p)
O(nlogp),数据大的时候可能会TLE
故得用线性时间来求
时间复杂度: O ( n ) O(n) O(n)
Code
inv[1] = 1;
for (int i = 2; i <= n; ++i) inv[i] = (long long)(p - p / i) * inv[p % i] % p;
线性求任意n个数的逆元
Idea
若要求任意n个数 ( a 1 , a 2 . . . a n ) (a_1,a_2...~a_n) (a1,a2... an) 的逆元 ( 1 ≤ a i < p 1 \leq a_i < p 1≤ai<p)的逆元, O ( m a x { a i } ) O(max\left\{a_i\right\}) O(max{ai}) 的复杂度显然不行
就需要使用下面的方法:
先计算出
n
n
n 个数的前缀积
s
i
s_i
si, 然后用exgcd / qpow 计算
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} * sv_i si−1∗svi 求得
所以我们就在几乎线性的时间内计算出了 n n n 个数的逆元
时间复杂度: O ( n + log p ) O(n + \log p) O(n+logp)
Code
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);
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;