我是靠谱客的博主 超帅高跟鞋,这篇文章主要介绍乘法逆元算法,现在分享给大家,希望可以做个参考。

逆元的定义

对于正整数 a a a m m m,如果有 a × x ≡ 1 ( m o d   m ) atimes xequiv1(mod:m) a×x1(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} a1

逆元的性质

假设存在三个剩余类 [ 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<a1<m的这个数称之为逆元。

另外,逆元具有积性: ( a b ) − 1 ≡ a − 1 × b − 1 ( m o d   m ) (ab)^{-1}equiv a^{-1}times b^{-1}(mod:m) (ab)1a1×b1(modm)

求一个数的逆元

扩展欧几里得算法求逆元

对于 a × x ≡ 1 ( m o d   m ) atimes xequiv1(mod:m) a×x1(modm)我们可以做一个公式推导:
a × x ≡ 1 ( m o d   m ) atimes xequiv1(mod:m) a×x1(modm);
( a × x − 1 ) % m = 0 (atimes x-1)%m=0 (a×x1)%m=0;
存在一个正整数满足 a × x − 1 = m × k atimes x-1=mtimes k a×x1=m×k;
a × x − m × k = 1 atimes x-mtimes k=1 a×xm×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的逆元。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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 am2modm

推导公式: 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) am11(modm)=a×am21(modm)=am2a1(modm)。所以 a a a的逆元为 a p − 2 a^{p-2} ap2。从这里我们也可以看出使用费马小定理求逆元的精华就是快速幂。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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+c0(modp);
式子两边同时乘以 i − 1 × c − 1 i^{-1}times c^{-1} i1×c1( i − 1 , c − 1 i^{-1},c^{-1} i1,c1都是模意义下的逆元)
所以我们有 k × c − 1 + i − 1 ≡ 0 ( m o d   p ) ktimes c^{-1}+i^{-1}equiv0(mod:p) k×c1+i10(modp);
i − 1 ≡ − k × c − 1 ( m o d   p ) i^{-1}equiv-ktimes c^{-1}(mod:p) i1k×c1(modp);
i − 1 ≡ − ( p / i ) × ( p % i ) − 1 ( m o d   p ) i^{-1}equiv-(p/i)times(p%i)^{-1}(mod:p) i1(p/i)×(p%i)1(modp);

所以我们的 i − 1 i^{-1} i1就可以用 ( p % i ) − 1 (p%i)^{-1} (p%i)1来推出,所以就可以用递推式求出来 1 1 1 i i i之间所有数的逆元。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#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} bn1的积的逆元,记为 a r r n − 1 arr_{n-1} arrn1。同理我们可以一次计算出所有的 a r r i arr_i arri,于是就可以用 a i − 1 × a r r i a_{i-1}times arr_i ai1×arri求得。时间复杂度为 O ( n + l o g n ) O(n+log_n) O(n+logn)

如果你还是不理解我们可以使用图标的形式让你明白。

在这里插入图片描述

大家可以自行对照图标和代码推一边。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#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; }

最后

以上就是超帅高跟鞋最近收集整理的关于乘法逆元算法的全部内容,更多相关乘法逆元算法内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(42)

评论列表共有 0 条评论

立即
投稿
返回
顶部