算法笔记:乘法逆元
一,乘法逆元
乘法逆元这个东西貌似高中数学都不会讲到
你们可以看一下百度百科,里面的解释就是↓
群G中任意一个元素a,都在G中有唯一的逆元a‘,具有性质aa'=a'a=e,其中e为群的单位元。
但是这个解释貌似连我都看不懂……
但是我们只需要知道逆元就是一个x使a*x≡1(mod p)
二,乘法逆元的性质
性质1:存在唯一性
我们假设a有相同的两个逆元a'和a''
那么:
a*a'≡a*a''≡1(mod p)
所以不妨设a'>a'',并且a'-a''=k
由于a≠0,所以k一定等于0(mod p),所以a'=a''
与假设矛盾,所以每一个a只有对应的一个逆元a'
性质2:完全积性函数
为了接下来的表示方便我们把a的逆元表示为inv[a]
这个性质就是说:inv[a]*inv[b]=inv[a*b]
那么这个怎么证明呢???
∵a*inv[a]≡b*inv[b]≡1(mod p)
∴a*inv[a]*b*inv[b]≡1*1(mod p)
∴(a*b)*(inv[a]*inv[b])≡1(mod p)
∴inv[a]*inv[b]=inv[a*b]
性质3:a*inv[b]≡a/b(mod p)
∵b*inv[b]≡1(mod p)
∴a*b*inv[b]≡a(mod p)
∴ a*inv[b]≡a/b(mod p)
这个性质很重要!
有的时候我们要求a/b mod p的值,我们只能在a上不断加上p,直到整除b
如果a,b,p都很大那么这个算法就咕掉了
所以这样我们只需要用逆元就能很快求出
三,那么乘法逆元怎么求呢???
方法1:枚举法(单个)
枚举x使a*x≡1(mod p),怎么说呢???
太蠢了叭……(出题人:给爷爪巴
方法2:费马小定理(单个)
由于费马小定理的存在↓(摘自百度百科)
费马小定理(Fermat's little theorem)是数论中的一个重要定理,在1636年提出。如果p是一个质数,而整数a不是p的倍数,则有a^(p-1)≡1(mod p)。
我们可以把a^(p-1)≡1(mod p)拆成a*a^(p-2)≡1(mod p)
所以说a^(p-2)就是a mod p的乘法逆元
然后快速幂求出a^(p-2)即可,代码就不展示了
方法3:扩展欧几里得(单个)
找到a*x≡1(mod p)相当于求出a*x+p*y=1的解
然后于是我们再看到a,p一定互质,所以说我们就能用拓展欧几里得求出x和y
于是x,就是a的逆元
四,如何批量求逆元
当我们看到上两三种方法之后,我们发现:
如果要批量求逆元,他们还会加一个O(n)的时间复杂度
所以我们现在就要讨论那些可以批量解决逆元的方法
方法4:欧拉筛(批量)
这里就用到了完全积性函数,所以说要求这个数的逆元就等于他的所有质因子的逆元的乘积
但如果是质数就需要用快速幂或者扩欧解决
代码:
#include<bits/stdc++.h> using namespace std; const int N=25000528; int p,n; int vis[N],pri[N],inv[N]; int ksm(int a,int b,int p) { int ans=1; while(b) { if(b&1) ans=ans*a%p; b>>=1; a=a*a%p; } return ans; } int main() { cin>>n>>p; vis[1]=1,inv[1]=1; for(int i=2;i<=p-1;i++) { if(!vis[i]) pri[++pri[0]]=i,inv[i]=ksm(i,p-2,p); for(int j=1;j<=pri[0];j++) { if(i*pri[i]>=p) break; inv[i*pri[j]]=inv[i]*inv[pri[j]]; if(i%pri[j]==0) break; } } for(int i=1;i<=n;i++) printf("%d\n",inv[i]); return 0; }
方法5:线性递推(批量)
现在我们要求k的逆元。
首先1^-1≡1(mod p)
然后我们设p=k*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^-i≡-(p/i)*(p%i)^-1(mod p)
所以易得转移方程:
inv[0]=inv[1]=1; for(int i=2;i<N;i++) inv[i]=((mod-mod/i)*_inv[mod%i])%mod;
于是全部的批量求逆元就讲完了
然后,就不知道讲什么了
看在码了这么多字,给个赞吧qwq