数学基础:最大公约数(GCD)、最小公倍数(LCM)、求逆元

借助欧几里得算法(辗转相除法),可以使GCD问题的求解大大简化。a、b全0则最大公约数不存在;有一个为0,最大公约数为另一个;否则就模运算并交换值,一直处理。C++STL库中内置__gcd(a,b)可直接求出gcd。

//递归
int getGCD(int a,int b){
  if(b==0){
    return a;
  }
  return getGCD(b,a%b);
}
//或是循环
int getGCD(int a,int b){
  while(b!=0){
    int t=a%b;
    a=b;
    b=t;
  }
  return a;
}

LCM(a,b)=a*b/GCD(a,b)。设k=a*b,则k/c=a*(b/c),k/c=b*(a/c),当c最大且能被a、b都整除时,得到k/c即为最小公倍数。

接下来介绍一下扩展欧几里得算法。之前在密码学这门课上也学过,主要是用来解同余方程a*x≡1(mod b),求出的x=a'即为a的逆元;或是a*x+b*y=gcd(a,b)的所有解

根据欧几里得公式,gcd(a,b)=gcd(b,a%b)。由此式将两个式子进行连接:a*x1+b*y1=b*x2+(a%b)*y2

由于a%b=a-b*(a/b),所以上式变为a*x1+b*x1=b*x2+(a-b*(a/b))*y2,再对右侧进行处理变为a*x1+b*y1=a*y2+b*(x2-(a/b)*y2)

到了这一步,可以得出x1y1与x2y2之间的关系式,x1=y2;y1=x2-(a/b)*y2

根据欧几里得求gcd的过程,当b=0时,gcd(a,b)=a。此时对a*x+b*y=gcd(a,b)可以得到一组特解,x0=1,y0=0(y0不影响)。根据我们之前得到的关系式,可以向上回溯,当回溯完成也就得到了想要的一组解。

但是需要注意,这组解未必是最小的正整数,此时需要额外处理。x=(x+b)%b,y=(y+b)%b。同样的也可以得出所有的解,对于x和y同时进行操作:x+=b/g,y+=a/g,相当于原式左侧加同时减了一个LCM。由此可得到所有的解。

更一般的情况,对于a*x+b*y=c,先解出a*x'+b*y'=gcd(a,b)的解。令t=c/gcd(a,b),则原式的解为x=t*x',y=t*y'。

给出例题,洛谷P1082,解同余方程。

#include<bits/stdc++.h>
using namespace std;

void exgcd(long long a,long long b,long long &gcd,long long &x,long long &y)
{
    if(b==0){
        x=1;
        y=0;
        gcd=a;
    }
    else{
        exgcd(b,a%b,gcd,y,x);
        y-=x*(a/b);
    }
}
int main()
{
    long long a,b,x,y,gcd;
    scanf("%lld%lld",&a,&b);
    exgcd(a,b,gcd,x,y);
    printf("%lld\n",(x+b)%b);
    return 0;
}

对于求逆元,也就是解a*x≡1(mod p),其实有多种做法。上面是介绍了扩展欧几里得的做法,接下来介绍其他方法。根据题意选择合适的方式

(1)扩展欧几里得:面对求单个数的逆元时,具有优势

(2)快速幂,利用费马小定理

若p为素数,a为正整数,且a、p互质。 则有ap−1≡1( mod  p)

所以,ax≡ap-1(mod p)。得出x≡ap-2(mod p),到这一步利用快速幂可以求出逆元

(3)线性算法:处理一连串数字的逆元

设 p=k*i+r(1<r<i<p), 其中 k 是 p/i 的商,r 是余数 。将该式子放入mod p的情境下,k*i+r≡0(mod p)。左右同乘i-1r-1,变为k*r-1+i-1≡0(mod p)。i-1≡-(p/i)*(k%i)-1
根据以上的关系,可得到一个递推式inv[i]=(p-p/i)*inv[p%i]%p。inv[0]=0,inv[1]=1,先算完前面再算后面,由此可得一组连续数字的逆元。

(4)求阶乘逆元

inv[i+1]=1/(i+1)!,inv[i]=1/i!=(i+1)*inv[i+1]

由此,可以先算出n!的逆元,则可以递推得到1,2!,......,(n-1)!的逆元

 

posted @ 2020-10-09 11:18  太山多桢  阅读(992)  评论(0)    收藏  举报