乘法逆元 | 分数 (小数) 取模
作用
比赛中大多数情况为了避免大整数运算,大多都会要求答案对一个数(大多是质数)取模。
-
题目中用到除法时
因为$\frac{a}{b} \% p \neq (\frac{a\%p }{b\%p}) \%p $ ,此时就无法计算,需要用到乘法逆元 -
当题目遇到小数时
因为取模运算时对于整数来说的,所以无法计算,需要用到乘法逆元。
虽然c语言有fmod函数,但是函数fmod后结果还是小数,如果题目要求mod后为整数,还需要用到逆元。
定义
逆元素(简称逆元) 是指一个可以取消另一给定元素运算的元素,如加法中的加法逆元(相反数)和乘法中的倒数。简单来说,A的逆元就是能抵消A的影响的元素。
如果说 a 在模 p 意义下的乘法逆元是 x,
那么 \(ax \equiv 1 \pmod{p}\)
(ax模p等于1模p)
不难发现,如同 0 没有倒数一样,
0没有乘法逆元
。
a 在模 p 意义下的乘法逆元均为 [1,p−1] 中的整数。
然后,因为求模意义下的a/b,就是求\(bc \equiv a \pmod{p}\)的c,
这时给a乘上b的逆元就消去了乘b的影响,结果为c,即
即模意义下除以一个数就等价于乘这个数的乘法逆元
。
性质
-
存在唯一性:对 a 来说,若它有逆元,则在p范围内一定只有一个逆元。
-
a 在模 p 意义下的乘法逆元存在,当且仅当 a,p 互质。
这也在一定程度上解释了大多数题目模数为什么为质数的原因:(设模数为 p)可以保证在$ [1,p - 1]$ 中的所有整数都有模 p 意义下的逆元。
求逆元
快速幂求逆元
对于\(a x\equiv 1 \pmod {p}\)求x
因为: a 在模 p 意义下的乘法逆元存在,当且仅当 a,p 互质。
有费马定理得到:
那么
那么a的逆元就是:\((a^{p-2}) \mod p\)
代码:
int qmi(int a, int k, int p)
{
int res = 1;
while(k)
{
if(k & 1) res = res * a % p;
k >>= 1;
a = a * a % p;
}
return res;
}
int get(int a,int p){
if(a % p == 0) return -1;
else return qmi(a,p-2,p);
}
拓展欧几里得求逆元
补充:下面3个公式都成立
不难发现\(ax \equiv 1 \pmod {p}\)等价于\(ax + py = 1\)
点击查看证明
将同余号换为等号得:
移项得:
由上式可知\(ax - 1\)为 p 的整数倍,不妨设其为 p的k倍$ (k \in Z)$:
继续移项得:
令 \(y = -k\)就写成了上文的形式:
又因为乘法逆元存在,当且仅当 a,p 互质,即$ gcd(a,p) = 1$。
故我们可以直接运用拓展欧几里得算法解 \(ax + py = gcd(a,p)\)这个方程。
时间复杂度: \(log(p)\)
手算模拟:
整个求解的过程就是使用辗转相除法求两个数的公约数,并且我们知道公约数的结果为1,所以一直计算到1为止即可。
例1:求在p = 14的条件下 a = 5的逆元
等号左边为余数
等号左侧得到1,进行等式代换:
得到结果为3。
例2:求在p = 18的条件下 a = 5的逆元
前面的步骤相同,使用辗转相除,会得到3个式子:
在代换时会发现一个问题,由于式子是奇数个,所以最后整理时a的系数为负:
此时将结果表示出来会是这个样子:
但是利用两个数互质的性质以及最小公倍数,我们可以直接得到想要的结果:
代码:
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
long long a, b;
//b是p,x是逆元
int exgcd(long long a, long long b, long long &x, long long &y)
{
if (b == 0)
{
x = 1, y = 0;
return a;
}
int d = exgcd(b, a % b, y, x);
y -= a / b * x;
return d;
}
int main()
{
scanf("%lld%lld", &a, &b);
long long x = 0, y = 0;
exgcd(a, b, x, y);
printf("%lld", (x % b + b) % b);//这里防止出现负数
return 0;
}