乘法逆元

逆元

什么是逆元

对于正整数\(a\)\(m\),如果有\(a\times x\equiv1(mod\:m)\),那么就把这个同余方程中\(x\)的最小正整数解叫做\(a\)\(m\)的逆元。

求一个数的逆元

扩展欧几里得算法求逆元

对于\(a\times x\equiv1(mod\:m)\)我们可以做一个公式推导:
\(a\times x\equiv1(mod\:m)\);
\((a\times x-1)\%m=0\);
存在一个正整数\(k\)满足\(a\times x-1=m\times k\);
\(a\times x-m\times k=1\)
即存在\(y=-k\)使得\(a\times x+m\times y=1\);
所以求\(a\)\(m\)的逆元,就是求方程式\(a\times x+m\times y=1\)的解,并且在实际使用中,一般把\(x\)的最小正整数称之为\(a\)\(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\)为素数,那么我们可以根据费马小定理得到你元为\(a^{m-2}\:mod\:m\)

推导公式:\(a^{m-1}\equiv1(mod\:m)=a\times a^{m-2}\equiv1(mod\:m)=a^{m-2}\equiv \frac{1}{a}(mod\:m)\)。所以\(a\)的逆元为\(a^{p-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;
}

如何求多数的逆元

线性求\(1\)\(n\)的逆元

对于求很多数的逆元的题目,我们可以使用线性求解逆元。
我们先看带余除法\(p=k\times i+c\);
我们可以把这个式子写成\(k\times i +c\equiv0(mod\:p)\);
式子两边同时乘以\(i^{-1}\times c^{-1}\)(\(i^{-1},c^{-1}\)都是模\(p\)意义下的逆元)
所以我们有\(k\times c^{-1}+i^{-1}\equiv0(mod\:p)\);
\(i^{-1}\equiv-k\times c^{-1}(mod\:p)\);
\(i^{-1}\equiv-(p/i)\times(p\%i)^{-1}(mod\:p)\);

所以我们的\(i^{-1}\)就可以用\((p\%i)^{-1}\)来推出,所以就可以用递推式求出来\(1\)\(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\)个数的前缀积,记为\(a_i\),然后使用快速幂或者是扩展欧几里得算法求解\(s_n\)的逆元,记为\(arr_n\)。因为\(arr_n\)\(n\)个数的逆元,所以当我们把它乘以\(b_n\)时,就会和\(b_n\)的逆元相互抵消,于是就得到了\(b_i\)\(b_{n-1}\)的积的逆元,记为\(arr_{n-1}\)。同理我们可以一次计算出所有的\(arr_i\),于是\(b_i^{-1}\)就可以用\(a_{i-1}\times arr_i\)求得。时间复杂度为\(O(n+log_n)\)

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

0 1 2 3
\(b_i\) 0 \(b_1\) \(b_2\) \(b_3\)
\(a_i\) 1 \(b_1\) \(b_1b_2\) \(b_1b_2b_3\)
\(arr_i\) 1 \(b_1^{-1}\) \(b_1^{-1}b_2^{-1}\) \(b_1^{-1}b_2^{-1}b_3^{-1}\)
结果 \(b_1^{-1}\) \(b_2^{-1}\) \(b_3^{-1}\)

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

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

如果以上内容有错误的地方,请在下面评论指出,谢谢了!!!

posted @ 2021-05-19 20:47  h星宇  阅读(135)  评论(0编辑  收藏  举报