首页 > 编程语言 >算法学习笔记-逆元

算法学习笔记-逆元

时间:2023-08-17 16:47:03浏览次数:54  
标签:frac int exgcd 算法 逆元 笔记 在模 mod

前言:

还记得小学学的倒数吗?倒数的定义大概是若 \(ax = 1\),则称 \(x\) 为 \(a\) 的倒数。而逆元,其实可以看做在模意义下的倒数。也就是 \(ax \equiv 1 \pmod p\),且 \(a\) 与 \(p\) 互质,则称 \(x\) 为 \(a\) 在模 \(p\) 意义下的乘法逆元,记作 \(a^{-1}\)。本文就将简要介绍求逆元的三种常用方法。

例题:

给定正整数 \(n\) 和 \(p\),求 \(n\) 以内正整数在模 \(p\) 意义下的乘法逆元。

保证 \(p\) 是质数。

解法:

1.费马小定理:

费马小定理的定义:

对于任意正整数 \(a\) 和质数 \(p\),有 \(a^{p-1} \equiv 1\pmod p\)。

可以发现,费马小定理的式子很像逆元,考虑把它转化一下,变成:

\(a \times a ^ {p-2} \equiv 1\pmod p\)。

可以发现,\(a^{p-2}\) 就等于上面的 \(x\),即 \(a^{p-2}\) 就是 \(a\) 在模 \(p\) 意义下的乘法逆元,用快速幂求即可。

点击查看代码
int qpow(int a, int b, int mod)
{
	int ans = 1;
	a %= mod;
	while(b)
	{
		if(b & 1) ans *= a;
		a = a * a % mod;
		b >>= 1; 
	}
	return ans;
}

int inv(int a, int p)//求a在模p意义下的乘法逆元
{
	return qpow(a, p - 2, p);
} 

2.\(\operatorname{Exgcd}\):

不知道 \(\operatorname{exgcd}\) 的可以参考我的博客

考虑将原式转换为 \(ax+py=1\) (注:\(y\) 是引入的一个整数,而且可能是负数),根据逆元的定义:\(a\),\(p\) 必须互质,也就是 \(\gcd(a, p) = 1\),因此原方程一定有解,用 \(\operatorname{exgcd}\) 求即可。

点击查看代码
void exgcd(int a, int b, int &x, int &y)
{
	if(b == 0)
	{
		x = 1; y = 0;
		return;
	}
	exgcd(b, a % b, y, x);
	y = a / b * x;
}
int inv(int a, int p)//求a在模p意义下的乘法逆元
{
	int x, y;
	return exgcd(a, p, x, y);
} 

3.线性递推:

很多时候,题目要求的都是像例题那样 \(1\)~\(n\) 的逆元,用上面两种办法可以达到较快的 \(O(n\log n)\) 的复杂度(其实费马小定理得复杂度应该为 \(O(n\log q)\)。不过有没有方法能在线性时间内求出呢?答案是有的。

注:下面笔者提供了两种介绍方法,前一种为严谨的数学证明,后一种是转化为 \(\operatorname{exgcd}\) 的介绍,请读者根据需要自行阅读。

  1. 数学证明:

先给结论:\( \begin{cases} x=1 & a=1 \\ -\frac{q}{a}(p\mod a)^{-1} & otherwise \\ \end{cases} \)

先把式子转化一下:

设 \(k=\frac{p}{a}\), \(b=p\mod a\)。

则 \(a=\frac{p-b}{k}\)

则原式就可以变成:

\(a(-\frac{p}{a}(p\mod a)^{-1})=a(-kb^{-1})\)

打开括号,变成:

\(-xkb^{-1}=-k\frac{m-b}{k}b^{-1}\)

把 \(k\) 消掉,得到:

\((b-p)b^{-1}\)

因为对于同余方程,增减带模数的项结果不发生改变,所以可以将 \(p\) 去掉,变为:

\((b-p)b^{-1} \equiv bb^{-1} \equiv1 \pmod m\)

换句话说,\(a(-\frac{q}{a}(q\mod a)^{-1})=a(-kb^{-1}) \equiv1 \pmod m\),即 \(-\frac{q}{a}(q\mod a)^{-1})=a(-kb^{-1}=a^{-1}\)。】

  1. 其实仔细观察可以发现,上式其实与 \(\operatorname{exgcd}\) 的结论的式子很像。加上之前逆元对 \(\operatorname{exgcd}\) 的转化,就显而易见了,具体一点笔者也说不清,请读者自行脑补(大雾。

知道上面的结论,代码就好写了。用一个数组记录每个数的逆元,正序枚举,由于枚举到 \(i\) 时,\(1\)~\(i-1\) 的逆元都已算出,因此 \(i\mod p\) 的逆元也能求出,便可以做到单次 \(O(1)\),总复杂度为 \(O(n)\)。

1

点击查看代码
inv[1] = 1;//inv[i] = i^ -1  
cout << 1 << "\n";
for(int i = 2; i <= n; i++)
{
	inv[i] = (p - p / i) * inv[p % i] % p;//按公式计算。
	//之所以要p-p/i是因为除完之后是个负数,在后面的取模会有问题,因此按照负数取模的原理要减一下 
	cout << inv[i] << "\n";
}

总结:

至此,逆元就告一段落了,文中的三种方法都有不错的时间复杂度,在应用中请根据实际情况选择,希望本文能帮助到您。

鉴于笔者也是蒟蒻,文中给出的部分推导过程与表述可能不严谨,欢迎大佬在评论区指出。
同时如果您对文章内容有不理解的地方,欢迎随时提问。

标签:frac,int,exgcd,算法,逆元,笔记,在模,mod
From: https://www.cnblogs.com/zhangxiao666qwq/p/suanfa-niyuan.html

相关文章

  • 数组下标中值求取算法
    问题解法一1.先计算出所需区间的大小10-2=82.计算当前区间的中值8/2=43.用区间起点加上中值,即为真实的中间值2+4=6完整公式是(end-start)/2+start解法二1.前置扩充所需区间start大小2.后置扩充所需区间start大小3.新的区间大小是12,那么中间值就是6完整公式是(start+e......
  • 笔记整理--C语言--assert用法总结——转载
    assert用法总结assert宏的原型定义在<assert.h>中,其作用是如果它的条件返回错误,则终止程序执行,原型定义:#include<assert.h>voidassert(intexpression);assert的作用是现计算表达式expression,如果其值为假(即为0),那么它先向stderr打印一条出错信息,然后通过调用abort来......
  • 【航迹】基于MN逻辑算法实现航迹关联和卡尔曼滤波外推附matlab代码
    ✅作者简介:热爱科研的Matlab仿真开发者,修心和技术同步精进,matlab项目合作可私信。......
  • 路径规划算法:基于绯鲵鲣算法的机器人路径规划算法- 附matlab代码
    ✅作者简介:热爱科研的Matlab仿真开发者,修心和技术同步精进,matlab项目合作可私信。......
  • 暑期AI夏令营,机器学习笔记
    打卡第一天时间2023-8-17学习内容如何部署、运行baseline选择运行环境:V10032GB点击运行全部cell获得submit.csv文件如何进行成绩的提交实际上提交的是submit.csc文件先右键此文件点击下载进入https://challenge.xfyun.cn/topic/info?type=subscriber-addition-......
  • Tarjan学习笔记
    TarjanTarjan算法是图论中非常常用的算法之一,能解决强连通分量,双连通分量,割点和桥,求最近公共祖先(LCA)等问题。Tarjan算法是基于深度优先搜索的算法,用于求解图的连通性问题。割点如果从图中删除节点\(x\)以及所有与\(x\)关联的边之后,图将被分成两个或两个以上的不相连的......
  • go语言学习笔记摘要
    引用:https://learnku.com/docs/the-way-to-go/variable/3585摘要点:1.变量命名规则:变量的命名规则遵循骆驼命名法,即首个单词小写,每个新单词的首字母大写。2.变量赋值::=: 它只能被用在函数体内,而不可以用于全局变量的声明与赋值多变量可以在同一行进行赋值, ......
  • 组合导航原理(七)——位姿算法更新总结
    IMU输出的是:t时刻的角度增量:Δθ(t)=∫wbib(τ)dτt时刻的速度增量:Δv(t)=∫fb (τ)dτt时刻的增量,是相对于t-1时刻而言,并不是初始时刻,这个要特别注意。 而角度增量Δθ(t)、速度增量Δv(t)中,抹掉了很多信息,比如:输出的蓝色的面积,但是曲线细节没有展现。以wbib为例,......
  • 笔记整理--C语言--高质量C编程指南—林锐——转载
    高质量C编程指南—林锐头文件的作用略作解释:通过头文件来调用库功能。在很多场合,源代码不便(或不准)向用户公布,只要向用户提供头文件和二进制的库即可。用户只需要按照头文件中的接口声明来调用库功能,而不必关心接口怎么实现的。编译器会从库中提取相应的代码。头文件能加强......
  • 背包算法
    转自:https://zhuanlan.zhihu.com/p/349054931https://blog.csdn.net/windfriendc/article/details/123892024......