我是靠谱客的博主 超帅高跟鞋,最近开发中收集的这篇文章主要介绍乘法逆元算法,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

逆元的定义

对于正整数 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的逆元。

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。从这里我们也可以看出使用费马小定理求逆元的精华就是快速幂。

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之间所有数的逆元。

#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)

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

在这里插入图片描述

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

#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;
}

最后

以上就是超帅高跟鞋为你收集整理的乘法逆元算法的全部内容,希望文章能够帮你解决乘法逆元算法所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

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

评论列表共有 0 条评论

立即
投稿
返回
顶部