逆元
逆元(Inverse element)就是在mod意义下,不能直接除以一个数,而要乘以它的逆元。
比如
a
∗
b
≡
1
(
m
o
d
p
)
a*b≡1(mod p)
a∗b≡1(modp),
a
,
b
a,b
a,b互为模
p
p
p意义下的逆元,就是当你想要求
x
/
a
x/a
x/a就可以改为求
x
∗
b
%
p
x*b\%p
x∗b%p
观察
a
∗
b
≡
1
(
m
o
d
p
)
a*b≡1(mod p)
a∗b≡1(modp),可以变形为
a
∗
b
+
k
∗
p
=
1
a*b+k*p=1
a∗b+k∗p=1,就可以用扩展欧几里得算法求
a
a
a了,同时这里也说明了
a
a
a和
p
p
p只有在互素的情况下才存在逆元。
注意
在计算 a / b a/b a/b时,算出 b b b关于 m o d mod mod的逆元后,最好再对 a a a求一下 a % m o d a\%mod a%mod后再相乘 a ∗ i n v ( b ) % m o d a*inv(b)\%mod a∗inv(b)%mod,防止爆 l o n g l o n g long long longlong。
扩展欧几里得求逆元模板
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 1e5 + 10;
ll a, mod = 1000000007, t;
ll ppow(ll a, ll b) {
ll ans = 1;
while(b > 0) {
if(b & 1)
ans = ans * a % mod;
a = a * a % mod;
b >>= 1;
}
return ans;
}
ll exgcd(ll a, ll b, ll &x, ll &y) {
if(b == 0) {
x = 1, y = 0;
return a;
}
ll t = exgcd(b, a % b, y, x);
y -= a / b * x;
return t;
}
int main() {
scanf("%lld", &t);
while(t--) {
scanf("%lld", &a);
ll an, y;
ll ni = ppow(2, a - 1);//求a关于mod的逆元
ll gcd = exgcd(ni, mod, an, y);
an = (an % mod + mod) % mod;//an就是求出来的逆元
printf("%lld\n", (an * (a % mod)) % mod);//a%mod防止爆ll
}
return 0;
}
性能分析:
- 时间复杂度:O(logn)(实际是斐波那契数列)
- 适用范围:只要存在逆元即可求,适用于个数不多但是mod很大的时候,也是最常见的一种求逆元的方法。
费马小定理/欧拉定理求逆元模板
费马小定理:若
p
p
p为素数,则有
a
p
−
1
≡
1
(
m
o
d
p
)
a^{p-1}≡1(modp)
ap−1≡1(modp)
a
p
−
2
∗
a
≡
1
(
m
o
d
p
)
a^{p-2}*a≡1(modp)
ap−2∗a≡1(modp)
则
a
p
−
2
a^{p-2}
ap−2就是
a
a
a在模
p
p
p意义下的逆元了。
欧拉定理:若a、p互素,则有
a
φ
(
p
)
≡
1
(
m
o
d
p
)
a^{φ(p)}≡1(modp)
aφ(p)≡1(modp)(费马小定理的一般形式)
a
φ
(
p
)
∗
a
≡
1
(
m
o
d
p
)
a^{φ(p)}*a≡1(modp)
aφ(p)∗a≡1(modp)
则
a
φ
(
p
)
−
1
a^{φ(p)-1}
aφ(p)−1就是
a
a
a在模
p
p
p意义下的逆元了。
ll qkpow(ll a, ll p, ll mod) {
ll t = 1, tt = a % mod;
while(p) {
if(p & 1)
t = t * tt % mod;
tt = tt * tt % mod;
p >>= 1;
}
return t;
}
ll getInv(ll a, ll mod) {
return qkpow(a, mod - 2, mod);
}
性能分析:
- O ( l o g m o d ) O(log^{mod}) O(logmod)
- 适用范围:一般在mod是个素数的时候用,比扩欧快一点而且好写。
- 但是如果是合数,相信一般没人无聊到去算个欧拉函数。
递推求逆元
ll inv[mod + 5];
void getInv(ll mod) {
inv[1] = 1;
for(int i = 2; i < mod; i++)
inv[i] = (mod - mod / i) * inv[mod % i] % mod;
}
注意:
- 调用前要先预处理
- 调用的时候要先对除数取 m o d mod mod
性能分析:
- 时间复杂度 O ( n ) O(n) O(n)
- 适用范围: m o d mod mod数是不大的素数而且多次调用,比如卢卡斯定理。