概述
逆元的定义
对于正整数 a a a和 m m m,如果有 a × x ≡ 1 ( m o d m ) atimes xequiv1(mod:m) a×x≡1(modm),那么就把这个同余方程中 x x x的最小正整数解叫做 a a a模 m m m的逆元。
根据前面的定义:对于模 m m m,若对于剩余类 [ a ] [a] [a],存在剩余类 [ b ] [b] [b]使得 [ a ] × [ b ] = [ 1 ] [a]times[b]=[1] [a]×[b]=[1],则称 [ b ] [b] [b]为 [ a ] [a] [a]的逆元。记作 a − 1 a^{-1} a−1。
逆元的性质
假设存在三个剩余类 [ a ] , [ b ] , [ c ] [a],[b],[c] [a],[b],[c], [ b ] , [ c ] [b],[c] [b],[c]同时是 [ a ] [a] [a]的逆元,且 [ b ] ≢ [ c ] ( m o d m ) [b]notequiv[c](mod:m) [b]≡[c](modm)。
则我们知道 [ b ] ≡ [ b ] × 1 ≡ [ b ] × ( [ a ] × [ c ] ) ≡ ( [ b ] × [ a ] ) × [ c ] ≡ [ c ] ( m o d m ) [b]equiv[b]times 1equiv[b]times([a]times[c])equiv([b]times[a])times[c]equiv[c](mod:m) [b]≡[b]×1≡[b]×([a]×[c])≡([b]×[a])×[c]≡[c](modm)。
所以它与条件相悖,故 [ b ] ≡ [ c ] ( m o d m ) [b]equiv[c](mod:m) [b]≡[c](modm),即我们知道,对于一个剩余类,它的逆元是唯一的。但是对于一个数,它的逆元是无穷多的。我们习惯将 0 < a − 1 < m 0<a^{-1}<m 0<a−1<m的这个数称之为逆元。
另外,逆元具有积性: ( a b ) − 1 ≡ a − 1 × b − 1 ( m o d m ) (ab)^{-1}equiv a^{-1}times b^{-1}(mod:m) (ab)−1≡a−1×b−1(modm)。
求一个数的逆元
扩展欧几里得算法求逆元
对于
a
×
x
≡
1
(
m
o
d
m
)
atimes xequiv1(mod:m)
a×x≡1(modm)我们可以做一个公式推导:
a
×
x
≡
1
(
m
o
d
m
)
atimes xequiv1(mod:m)
a×x≡1(modm);
(
a
×
x
−
1
)
%
m
=
0
(atimes x-1)%m=0
(a×x−1)%m=0;
存在一个正整数满足
a
×
x
−
1
=
m
×
k
atimes x-1=mtimes k
a×x−1=m×k;
a
×
x
−
m
×
k
=
1
atimes x-mtimes k=1
a×x−m×k=1;
即存在
y
=
−
k
y=-k
y=−k使得
a
×
x
+
m
×
y
=
1
atimes x+mtimes y=1
a×x+m×y=1;
所以求
a
a
a模
m
m
m的逆元,就是求方程式
a
×
x
+
m
×
y
=
1
atimes x+mtimes y=1
a×x+m×y=1的解,并且在实际使用中,一般把
x
x
x的最小正整数称之为
a
a
a模
m
m
m的逆元。
int n, a, b, p, x, y;
void exgcd(int a, int b){
if(b == 0){
x = 1;
y = 0;
return ;
}
exgcd(b,a%b);
int temp = x;
x = y;
y = temp - a / b * y;
}
int main(){
cin >> a >> b;
exgcd(a,b);//此时得到的x是方程的一个解,但不一定是方程的最小正整数解,x可能为负
x = (x % b + b) % b; //(x % m + m) % m 是方程最小正整数解,也就是a模m的逆元
cout << x;
return 0;
}
费马小定理求解逆元
逆元一般用扩展欧几里得算法来求解,但如果 m m m为素数,那么我们可以根据费马小定理得到逆元为 a m − 2 m o d m a^{m-2}:mod:m am−2modm。
推导公式: a m − 1 ≡ 1 ( m o d m ) = a × a m − 2 ≡ 1 ( m o d m ) = a m − 2 ≡ 1 a ( m o d m ) a^{m-1}equiv1(mod:m)=atimes a^{m-2}equiv1(mod:m)=a^{m-2}equiv frac{1}{a}(mod:m) am−1≡1(modm)=a×am−2≡1(modm)=am−2≡a1(modm)。所以 a a a的逆元为 a p − 2 a^{p-2} ap−2。从这里我们也可以看出使用费马小定理求逆元的精华就是快速幂。
typedef long long ll;
ll fastpow(ll n, ll a, ll mod){
if(n < 0) return 1;
ll res = 1;
a = a % mod;
while(n){
if(n & 1) res = (res * a) % mod;
a = (a * a) % mod;
n >>= 1;
}
return res % mod;
}
int main(){
cin >> a >> p;
cout << fastpow(p-2,a,p) << endl;
return 0;
}
如何求多数的逆元
线性求到的逆元
对于求很多数的逆元的题目,我们可以使用线性求解逆元。
我们先看带余除法
p
=
k
×
i
+
c
p=ktimes i+c
p=k×i+c;
我们可以把这个式子写成
k
×
i
+
c
≡
0
(
m
o
d
p
)
ktimes i +cequiv0(mod:p)
k×i+c≡0(modp);
式子两边同时乘以
i
−
1
×
c
−
1
i^{-1}times c^{-1}
i−1×c−1(
i
−
1
,
c
−
1
i^{-1},c^{-1}
i−1,c−1都是模意义下的逆元)
所以我们有
k
×
c
−
1
+
i
−
1
≡
0
(
m
o
d
p
)
ktimes c^{-1}+i^{-1}equiv0(mod:p)
k×c−1+i−1≡0(modp);
i
−
1
≡
−
k
×
c
−
1
(
m
o
d
p
)
i^{-1}equiv-ktimes c^{-1}(mod:p)
i−1≡−k×c−1(modp);
i
−
1
≡
−
(
p
/
i
)
×
(
p
%
i
)
−
1
(
m
o
d
p
)
i^{-1}equiv-(p/i)times(p%i)^{-1}(mod:p)
i−1≡−(p/i)×(p%i)−1(modp);
所以我们的 i − 1 i^{-1} i−1就可以用 ( p % i ) − 1 (p%i)^{-1} (p%i)−1来推出,所以就可以用递推式求出来 1 1 1到 i i i之间所有数的逆元。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define endl 'n'
const int N = 3e6+10;
ll arr[N];
int main(){
ll n, p;
cin >> n >> p;
arr[1] = 1;
cout << arr[1] << endl;
for(ll i = 2; i <= n; i++){
arr[i] = (-(p / i) * arr[p % i] % p + p) % p;//防止结果为负数
cout << arr[i] << endl;
}
return 0;
}
线性求任意个数的逆元
首先,我们需要求出 n n n个数的前缀积,记为 a i a_i ai,然后使用快速幂或者是扩展欧几里得算法求解 s n s_n sn的逆元,记为 a r r n arr_n arrn。因为 a r r n arr_n arrn是 n n n个数的逆元,所以当我们把它乘以 b n b_n bn时,就会和 b n b_n bn的逆元相互抵消,于是就得到了 b i b_i bi到 b n − 1 b_{n-1} bn−1的积的逆元,记为 a r r n − 1 arr_{n-1} arrn−1。同理我们可以一次计算出所有的 a r r i arr_i arri,于是就可以用 a i − 1 × a r r i a_{i-1}times arr_i ai−1×arri求得。时间复杂度为 O ( n + l o g n ) O(n+log_n) O(n+logn)。
如果你还是不理解我们可以使用图标的形式让你明白。
大家可以自行对照图标和代码推一边。
#include <bits/stdc++.h>
#include <map>
using namespace std;
typedef long long ll;
#define endl 'n'
#define IOS ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
#define _for(i, a, b) for (int i=(a); i<=(b); i++)
const int INF = 0x7fffffff;
const int MAXN = 1e4 + 10;
const ll mod = 1e9 + 7;
ll n, p, b[MAXN], a[MAXN], arr[MAXN], inv[MAXN];
ll fastpow(ll n, ll a, ll p){
ll res = 1;
while(n){
if(n & 1) res = ((res % p) * (a % p)) % p;
a = ((a % p) * (a % p)) % p;
n >>= 1;
}
return res % p;
}
int main(){
cin >> n >> p;
for(int i = 1; i <= n; i++){
cin >> b[i];
}
a[0] = 1;
for(int i = 1; i <= n; i++){
a[i] = a[i - 1] * b[i] % p;
}
arr[n] = fastpow(p-2,a[n],p);
for (int i = n; i >= 1; --i) arr[i - 1] = arr[i] * b[i] % p;
for (int i = 1; i <= n; ++i) inv[i] = arr[i] * a[i - 1] % p;
for(int i= 1; i <= n; i++){
cout << inv[i] << " ";
}
return 0;
}
最后
以上就是超帅高跟鞋为你收集整理的乘法逆元算法的全部内容,希望文章能够帮你解决乘法逆元算法所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复