乘法逆元
1定义
乘法逆元,是指数学领域群G中任意一个元素a,都在G中有唯一的逆元a‘,具有性质a×a'=a'×a=e,其中e为该群的单位元。
定理:a存在模p的乘法逆元的充要条件是gcd(a,p) = 1
证明: 首先证明充分性 如果gcd(a,p) = 1,根据欧拉定理,aφ(p) ≡ 1 mod p,因此 显然aφ(p)-1 mod p是a的模p乘法逆元。 再证明必要性 假设存在a模p的乘法逆元为ba ≡ 1 mod p 则 1 = ba - qp
根据贝祖定理,这个式子有整数解的充要条件是a和p的最大公约数整除1,故必须要满足gcd(a,p) = 1
举个例子,在模10的意义下,3的逆元为7,因为37与1在模10的意义下同余。
2.计算
下面的逆元均指在模运算下的逆元。
2.1费马小定理
根据费马小定理,如果p是质数,有\(a^{p-1}\equiv1(mod p)\),其中a是整数,所以\(a^{p-2}\)为a的逆元,用快速幂即可,注意取模,这个逆元在模p状态下唯一。
代码:
#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<sstream>
#include<queue>
#include<map>
#include<vector>
#include<set>
#include<deque>
#include<cstdlib>
#include<ctime>
#define dd double
#define ll long long
#define ull unsigned long long
#define N 110
#define M 20
using namespace std;
inline ll ksm(ll a,ll b,ll m){
ll res=1;
while(b){
if(b&1) res=(res*a)%m;
a=(a*a)%m;
b>>=1;
}
return res%m;
}
int main(){
ll n,p;
scanf("%lld%lld",&n,&p);
for(int i=1;i<=n;i++){
printf("%lld\n",ksm(i,p-2,p));
}
}
2.2 扩展欧几里得算法
根据扩展欧几里得算法,知道\(a*x\equiv1(mod p)\)(其实等价于\(k*p-a*x=1\))有解,那么可以通过扩展欧几里得算法求出。这里不再赘述。
#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<sstream>
#include<queue>
#include<map>
#include<vector>
#include<set>
#include<deque>
#include<cstdlib>
#include<ctime>
#define dd double
#define ll long long
#define ld long double
#define ull unsigned long long
#define N number
#define M number
using namespace std;
inline ll exgcd(ll a,ll b,ll &x,ll &y){
if(b==0){
x=1;
y=0;
return a;
}
ll gcd=exgcd(b,a%b,x,y);
ll tmp=x;
x=y;
y=tmp-a/b*y;
return gcd;
}
int main(){
ll a,b;
scanf("%lld%lld",&a,&b);
ll x,y;
exgcd(a,b,x,y);
printf("%lld",(x%b+b)%b);
}
2.3递推求逆元
首先,我们用\(a^{-1}\)表示a的逆元,易知\(1^{-1}=1\equiv1(mod p)\),所以1为1的逆元
还有递推公式证明:设\(q*k+r=p\)
则:\(q*k+r\equiv0(mod p)\)
\(k*r^{-1}+q^{-1}\equiv0(mod p)\)
\(q^{-1}\equiv{-k*r^{-1}}(mod p)\)
\(q^{-1}\equiv{(p-k)*r^{-1}}(mod p)\)
由此得出递推公式:inv[i] = (p - p / i) * inv[p % i] % p;(设inv[i]为i的逆元)
代码:
#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<sstream>
#include<queue>
#include<map>
#include<vector>
#include<set>
#include<deque>
#include<cstdlib>
#include<ctime>
#define dd double
#define ll long long
#define ull unsigned long long
#define N 4000010
#define M 20
using namespace std;
int inv[N];
int main(){
ll n,p;
scanf("%lld%lld",&n,&p);
inv[1]=1;
printf("%d\n",inv[1]);
for(int i=2;i<=n;i++){
inv[i]=(p-p/i)*inv[p%i]%p;
printf("%d\n",inv[i]);
}
return 0;
}