乘法逆元的三种求解方法

乘法逆元小结

参考自:点击此处

乘法逆元,一般用于求(a / b)(modp)的值(p 通常为质数),是解决模意义下分数数值的必要手段。
关于求余,有以下三种规则:
加法:(a+b)%m=(a%m+b%m)%m(a+b)%m=(a%m+b%m)%m
减法:(a−b)%m=(a%m−b%m)%m(a−b)%m=(a%m−b%m)%m
乘法:(a∗b)%m=(a%m∗b%m)%m(a∗b)%m=(a%m∗b%m)%m
但是这个规则在除法不适用,所以就要用到乘法逆元。

逆元的定义

若a ∗ x = 1 ( mod b ),且 a 与 b 互质,那么我们就能定义:x 为 a 的逆元,记为a−1
所以我们也可以称 x 为 a 在 mod b 意义下的倒数,
进而对于 a / b ( mod p ) ,我们可以求出 b 在 mod p 下的逆元,然后乘上 a ,再 mod p,就是要求的值了。

求解逆元的方法

1. 快速幂

想要了解快速幂的知识请点此处
时间复杂度:O(log n)
要利用到费马小引理:

若p为素数,a为正整数,且a、p互质
则有a ^ (p − 1) = 1 ( mod p )。

这个我们就可以发现它这个式子右边刚好为 1 。
所以我们就可以放入原式,就可以得到:
a ∗ x = 1 ( mod p )
a ∗ x = a ^ (p − 1) ( mod p )
x = a ^ (p − 2) ( mod p )
所以我们可以用快速幂来算出 a ^ (p - 2) ( mod p ) 的值,这个数就是a在mod p下的逆元了

测试代码
#include <iostream>
#include <cstdio>
using namespace std;
#define ll long long

ll quick_pow(ll x, ll n, ll mod)
{
	ll res = 1;
	while (n > 0)
	{
		if (n % 2 == 1)
		{
			res *= x % mod;
		}
		x *= x % mod;
		n >>= 1;	
	}
	return res;
}

int main(void)
{
	ll a, p;
	
	ll x = quick_pow(a, p - 2, p);
	x = (x % p + p) % p; //x为a在mod p意义下的逆元
	printf("%lld\n", x);  

	return 0;
}

2.拓展欧几里得

时间复杂度:O(log n)
这个方法十分容易理解,而且对于单个查找效率似乎也还不错,比大部分方法都要快(尤其对于 mod p 比较大的时候)。
这个就是利用拓欧求解 线性同余方程 a ∗ x = c (mod b) 的c = 1的情况。我们就可以转化为解 a ∗ x + b ∗ y = 1 这个方程。求解这个方程的解。不会拓欧可以点这里~
好处:当 a⊥p (互质),但 p 不是质数的时候也可以使用。

测试代码
#include <iostream>
#include <cstdio>
using namespace std;
#define ll long long
void Exgcd(ll a, ll b, ll &x, ll &y)
{
	if (!b)
		x = 1, y = 0;
	else
		Exgcd(b, a % b, y, x), y -= a / b * x;
}

int main(void)
{
	ll x, y;
	ll a, p;
	scanf("%lld%lld", &a, &p);
	Exgcd (a, p, x, y);
	x = (x % p + p) % p; // x为a在mod p意义下的逆元
	printf("%lld\n", x);
	return 0;
 }

3.线性算法

用于求一连串数字对于一个 mod p 的逆元
并且大多数题只能用这种方法,别的算法都比这些要求一串要慢。

首先我们有一个,1^(−1) = 1 (mod p)
然后设 p = k ∗ i + r, (1 < r < i < p) 也就是 k 是 p/i 的商,r 是余数 。
再将这个式子放到(mod p)意义下就会得到:
k ∗ i + r = 0(mod p)
然后乘上i ^ (-1), r ^ (−1)就可以得到:
k ∗ r − 1 + i − 1 = 0(mod p)
i ^ (-1) = (−k) ∗ r − 1 (mod p)
i ^ (-1) = − ⌊p / i⌋ ∗ (p mod i) ^ (-1) (mod p)
然后,我们就可以从前面推出当前的逆元了。

例题

洛谷P3811

AC代码
#include <iostream>
#include <cstdio>
using namespace std;
#define ll long long
const int MX = 3e6 + 5;
ll  inv[MX] = {0}; // 不用ll会溢出 
int main(void)
{
	int n, p;
	scanf("%d%d", &n, &p);
	inv[1] = 1;
	for (int i = 2; i <= n; ++ i)
	{
		inv[i] = (p - p/ i) * inv[p % i] % p;	
	}		
	for (int i = 1; i <= n; ++ i)
	{
		if(i != 1)
		{
			printf("\n");
		}
		printf("%d", inv[i]);
	}
	
	return 0;
} 

在这里插入图片描述

  • 8
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小陌白

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值