求解乘法逆元的方法

求解乘法逆元有三种常用方法:
1、费马小定理/欧拉函数
2、扩展欧几里德算法
3、线性求逆元

取模公式

( a + b )   m o d   p = ( a   m o d   p + b   m o d   p )   m o d   p (a+b)~mod~p=(a~mod~p+b~mod~p)~mod~p
( a b )   m o d   p = ( a   m o d   p b   m o d   p + p )   m o d   p (a-b)~mod~p=(a~mod~p-b~mod~p+p)~mod~p
( a × b )   m o d   p = ( a   m o d   p × b   m o d   p )   m o d   p (a\times b)~mod~p=(a~mod~p \times b~mod~p)~mod~p
( a ÷ b )   m o d   p = ( a × b φ ( p ) 1 )   m o d   p = ( a   m o d   p × b φ ( p ) 1   m o d   p )   m o d   p (a\div b)~mod~p=(a\times b^{\varphi(p)-1})~mod~p=(a~mod~p\times b^{\varphi(p)-1}~mod~p)~mod~p

乘法逆元

为了对除法算式进行取模,我们引入了乘法逆元的概念:

若存在正整数 a b p a、b、p 满足 a b 1 ( m o d   p ) ab≡1(mod~p) ,则称 a a b b 的乘法逆元,或称 b b a a 的乘法逆元

乘法逆元的存在性定理:

a a p p 互质,则⼀定存在⼀个正整数解 b b ,满⾜ b b < p p
a a p p 不互质,则⼀定不存在正整数解 b b

费马小定理

p p 为素数时,对于任意整数 x x 都有 x p x ( m o d   p ) x^{p}≡x(mod~p) ,此定理为费马小定理。

如果 x x 无法被 p p 整除,有 x p 1 1 ( m o d   p ) x^{p-1}≡1(mod~p)

等价为 x x p 2 1 ( m o d   p ) x*x^{p-2}≡1(mod~p)

可以得到 x 1 x p 2 ( m o d   p ) x^{-1}≡x^{p-2}(mod~p)

结论:当 p p 为素数时,有 ( a ÷ b )   m o d   p = ( a   m o d   p × b p 2   m o d   p )   m o d   p (a\div b)~mod~p=(a~mod~p\times b^{p-2}~mod~p)~mod~p

可以利用快速幂来求出逆元,复杂度为 O ( l o g n ) O(logn)

欧拉函数

φ ( m ) = \varphi(m)= 不超过 m m 并且和 m m 互素的数的个数

对于和 m m 互素的 x x ,有

x φ ( m ) 1   ( m o d   m ) x^{\varphi(m)}≡1~(mod~m)

等价为 x x φ ( m ) 1 1   ( m o d   m ) x*x^{\varphi(m)-1}≡1~(mod~m)

根据逆元的定义式,有

x 1 x φ ( m ) 1   ( m o d   m ) x^{-1}≡x^{\varphi(m)-1}~(mod~m)

结论: ( a ÷ b )   m o d   p = ( a × b φ ( p ) 1 )   m o d   p = ( a   m o d   p × b φ ( p ) 1   m o d   p )   m o d   p (a\div b)~mod~p=(a\times b^{\varphi(p)-1})~mod~p=(a~mod~p\times b^{\varphi(p)-1}~mod~p)~mod~p

求解 φ ( n ) \varphi(n) 的时间复杂度为 O ( n ) O(\sqrt n) ,求出 φ ( n ) \varphi(n) 后可以利用快速幂来求出逆元,用这种方法求逆元的时间复杂度为 O ( n l o g n ) O(\sqrt n*logn)

求解欧拉函数值的代码

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;

int euler_phi(int n)
{
	int res = n;
	for (int i = 2; i * i <= n; i++){
		if (n % i == 0){
			res = res / i * (i - 1);
			for ( ; n % i == 0; n /= i);
		}
	}	
	if (n != 1)
		res = res / n * (n - 1);
	return res;
}

int main(void)
{
	int n;
	cin >> n; 
	int ans = euler_phi(n);
	cout << ans << endl;
	return 0;
}

扩展欧几里得算法

常用于求 a x + b y = g c d ( a , b ) ax + by=gcd(a,b) 的一组可行解。返回值为 g c d ( a , b ) gcd(a,b) ,同时计算出 x x y y

可以用于求解逆元,根据逆元的定义: a x 1 ( m o d   b ) ax≡1(mod~b)

可以得出不定方程: a x + b y = 1 ax+by=1

g c d ( a , b ) = 1 gcd(a,b)=1 时,上述方程一定有解,所以一定存在一个 x x a a 的乘法逆元。

a b a、b 的值代入方程 a x + b y = 1 ax+by=1 ,就可以利用扩展欧几里得算法解出这个方程,解出的 x x 即为 a a 的乘法逆元。

用该算法求出的 x + y |x|+|y| 是最小的。如果 a b 0 ab\neq 0 ,还可以知道 x b y a |x|\leq b且|y|\leq a ,但并不能保证 x x 是正数,需要进行 x = ( b + x % b ) % b x=(b+x\%b)\%b 这样的操作来得到最小正整数 x x

这个算法的时间复杂度为 O ( l o g n ) O(logn)

扩展欧几里得算法求逆元的代码

#include <cstdio>
#include <iostream>
using namespace std;

int exgcd(int a, int b, int& x, int& y)
{
	int d = a;
	if (b != 0){
		d = exgcd(b, a % b, y, x);
		y -= (a / b) * x;
	}
	else{
		x = 1;
		y = 0;
	}
	return d;
}

int main(void)
{
	int a, b, x, y;
	cin >> a >> b;
	cout << exgcd(a, b, x, y) << endl;
	x = (b + x % b) % b;
	cout << x << endl;
	 
	return 0;
}

线性求逆元

此部分内容转载自:https://www.cnblogs.com/qdscwyy/p/7795368.html

p = k i + r ( r < i 1 < i < p ) p=k*i+r,(r<i,1<i<p)

将式子放到 m o d   p mod ~p 意义下,有
k i + r 0   ( m o d   p ) k*i+r≡0~(mod~p)
两边同乘 i 1 r 1 i^{-1}*r^{-1} ,得
k r 1 + i 1 0   ( m o d   p ) k*r^{-1}+i^{-1}≡0~(mod~p)
移项,得
i 1 k r 1   ( m o d   p ) i^{-1}≡-k*r^{-1}~(mod~p)
用已知的 p i p、i 表示 k r k、r ,得
i 1 p i ( p   m o d   i ) 1   ( m o d   p ) i^{-1}≡-\lfloor \frac{p}{i} \rfloor*(p~mod~i)^{-1}~(mod~p)

根据上面推出的公式,可以用前面的逆元推出当前的逆元

用式子表示时,负数取模的值等于加模数到正数后再取模的值,而写程序时不能直接用负数来计算,需要先加一个模数,就像下面这样

inv[i] = (p - (p / i) * inv[p % i]) % p;

此方法适用于求从1——n连续的逆元,时间复杂度为 O ( n ) O(n)

线性求解逆元的代码

#include <cstdio>
#include <iostream>
using namespace std;
const int N = 3e6 + 5;
typedef long long ll;

ll inv[N];

void pre(int p)
{
	inv[1] = 1;
	for (int i = 2; i <= N - 5; i++)
		inv[i] = ((p - p / i) * inv[p % i]) % p;
}

int main(void)
{
	int n, p;
	scanf("%d%d", &n, &p);
	pre(p);
	for (int i = 1; i <= n; i++)
		printf("%lld\n", inv[i]);
	
	return 0;
}
发布了199 篇原创文章 · 获赞 26 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/weixin_43772166/article/details/104254604