作为数论的基础,感觉逆元还是很有用的,基本取模和除法的地方就有它
首先这个东西的定义就是在模 p 意义下,a 乘上自己的逆元等于 1 ,就相当于模 p 意义下的倒数
共三种常用解法,我们今天还是注重一些实践方面,证明的话就略了(绝对不是我忘记了怎么证qwq 。 我们三个方法,各有好坏,也会在最后评述
费马小定理
这个是最简单的,当模数是一个质数 p ,a 关于 p 的逆元就是 \(a ^ {p-2}\) ,就这么轻松愉快
证法比较烦, 大概是构造一个余数集合之类的,个人认为不太重要(?
那么很显然这个可以用快速幂做到 \(O(\log N)\)
优点
- 编码极其简单,熟练后可以迅速使用
- 单词查询逆元时间复杂度优秀
缺点
- 条件苛刻
需要是一个质数模数,那么出题人毒瘤些整合数模数费马小定理就废了
当然,在大部分综合题里,费马小定理依然是应用场景最广泛的逆元求法
扩展欧几里得算法
这个其实学完扩欧理解很容易,没学过也不难。
扩欧其实只干了一件事,就是求
这么一个式子,然后像辗转相除一样,利用\(\gcd(a,b) = \gcd(b , a \% b)\)一路递归求一组特解
那么我们可以看问题了,相当于是求模 p 意义下
这个是不是直接代入就好了
p正负无关所以就等于
求完 x 的特解即为逆元
聪明的小朋友就看出来了,那你这个式子是不是必须得 \(\gcd(a,p) = 1\) 呢?对的对的,也就是求逆元的数要和模数互质
复杂度同欧几里得算法
优点
1.快
2.使用范围比费马小定理更加广泛
只要所求数与模数互质即可,这意味着如果 p 是一个巨大的合数,你就可以用这个求
缺点
1.编码其实不是很简单,代码虽短,如果不背,每次脑子过一遍还是有些麻烦
那么其实单次查询的方法就这俩,都是\(O(\log N)\) 的,如果要一次求一堆逆元呢?
递推
我们可以把模数 p 这样表示
那么 $$x \equiv \frac{p - r}{q} \equiv -\frac{r}{q}$$
很显然这个的逆元是\(- \frac{q}{r}\) ,搞一个数组 inv 存逆元,也就是 \((p / x)\times inv_{p \% x}\)
这个挺有趣的,可以放下代码
#include <bits/stdc++.h>
#define ll long long
const int N=3e6+7;
ll n,p; ll a[N];namespace P3811
{ inline void write(int x) {if(x<0)putchar('-'),x=-x; if(x>9)write(x/10); putchar(x%10+'0');return; }inline ll read() {int x=0,f=1; char ch=getchar();while(ch<'0'||ch>'9') {if(ch=='-') {f=-1;} ch=getchar();} while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar(); return x*f;}void main(){ n=read(),p=read(); a[1]=1;putchar('1'); putchar('\n');for(register ll i=2;i<=n;i++) {a[i]=(p-p/i)*a[p%i]%p; write(a[i]); putchar('\n');}}
}
int main(){P3811::main();}
即洛谷P3811
复杂度 \(O(N)\)
那么逆元就告一段落了