逆元
在费马小定理之前,我们先引入逆元的概念,考虑求解一个同余线性方程
ax≡b(modm)
,要怎样求解这个方程呢,在此之前,我们先看一下求解一般的线性方程的方法,一般地,我们能将一个一元一次方程化为如下的形式:
ax=b
,那么在a有倒数的情况下,我们可以轻松地解出这个方程,那么同余线性方程呢?假设我们有一个方程
ay≡1(modm)
,并记这个方程的解为
a−1
,即a的逆元,那么
x=a−1∗ax=a∗b
,即可求得方程的解.
现在的问题就变为了求解方程 ay≡1(modm) ,
假设我们已经求得该方程的解,那么肯定存在整数k,使其满足以下等式:
ay=mk+1
我们将式子稍稍变形:
ay−mk=1
就变成了求这个式子的解!是不是看起来很眼熟?其实就是之前讲到的扩展欧几里得算法.
用扩展欧几里得算法求解这个式子后,我们就能求a的逆元啦
代码如下
int extgcd(int a,int b,int &x,int &y){
int ans=a;
if(b==0){
x=1;
y=0;
}
else{
ans=extgcd(b,a%b,y,x);
y-=a/b*x;
}
return ans;
}
int mod_inverse(int a,int m){
int x;
int y;
extgcd(a,m,x,y);
return (m+x%m)%m;
}
模版题
http://acm.hdu.edu.cn/showproblem.php?pid=1576(HDU-1576)
题意
已知a%9973下的数n和b,a是b的倍数,且b与9973互质,求(a/b)%9973
题解
直接求带除法的取模运算是困难的,因为除法时取余与乘法,加法不同,可能改变结果,例如:
12/3%5=4
12%5/3=0.66666
因此我们考虑逆元,因为a是b的倍数,令 a=bx ,则 bx≡n(mod9973) ,而要求的答案就是 (a/b)%9973=(bx/b)%9973=x%9973 ,故只要求出b的逆元再乘上n即可求解
附AC代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long int LL;
const int MOD=9973;
int n;
int gcd(int a,int b){
if(b==0) return a;
return gcd(b,a%b);
}
int extgcd(int a,int b,int &x,int &y){
int ans=a;
if(b==0) {
x=1;
y=0;
}
else{
ans=extgcd(b,a%b,y,x);
y-=(a/b)*x;
}
return ans;
}
int mod(int b,int m){
int x,y;
extgcd(b,m,x,y);
return (m+(x*n)%m)%m;
}
int main(){
int T;
scanf("%d",&T);
while(T--){
LL b;
scanf("%d %lld",&n,&b);
printf("%d\n",mod(b,9973));
}
}
费马小定理
在上述背景下,我们引入费马小定理;
它的内容如下:
假设有一质数p与整数a,使得a,p互质(即gcd(a,p)=1),那么它们满足如下关系:
ap−1≡1(modp)
证明? 不会,留个坑先.
这个式子对于我们有什么好处,或者说有什么应用呢,我们对它稍稍变形:
ap−2≡a−1(modp)
其中 a−1 就是a的逆元,利用这个定理,我们可以利用快速幂快速求解特定情况下的逆元.
代码如下
int power_mod(int x,int n,int p){
int res=1;
while(n){
if(n&1) res=(res*x)%p;
x=(x*x)%p;
n>>=1;
}
return res;
}
int get_mod(int a,int p){
return power_mod(a,p-2,p);
}
费马小定理推广(欧拉定理)
费马小定理讨论的是p为质数的情况,对于p不是质数,我们可以将其推广至欧拉定理,其内容如下:
假设有两个整数a,n互质,那么它们有如下关系
aφ(n)≡1(modn)
其中 φ(n) 为欧拉函数,关于其的定义如下:
φ(n)=n∗∏(pi−1)/pi
因为n的整数分解可以在
O(n√)
时间内求得,因此求某一个数的欧拉函数的时间复杂度也是
O(n√)
求欧拉函数值代码
int eular_phi(int n){
int res=n;
for(int i=2;i*i<=n;i++){
if(n%i==0){
res=res/i*(i-1);
while(n%i==0){
n/=i;
}
}
if(n!=1) res=res/n*(n-1);
return res;
}
}
筛出欧拉函数值的表代码
利用埃拉托色尼筛法,我们可以O(n)实现筛表,之后O(1)查询,代码如下:
int eular[MAXN];
void eular_phi(){
for(int i=0;i<MAXN;i++){
eular[i]=i;
}
for(int i=2;i<MAXN;i++){
if(eular[i]=i){
for(int j=i;j<MAXN;j+=i){
eular[i]=eular[j]/i*(i-1);
}
}
}
}