菜鸡学习数论的第四天 之 逆元

逆元
  什么是逆元?逆元就是(a*b)%p=1,就称为b是a的逆元。
  逆元是用来做什么的?

在取模的时候,(a+b)%p=a%p+a%b;
              (a-b)%p=a%p-b%p; 
              (a*b)%p=a%p*b%p;

  但是除法就没有相应的规律,所以,我们在除法取模的时候,就能够乘上除数的逆元,将其变成乘法,这样就好取模了。

如何求逆元???
  逆元的存在:当且仅当gcd(a,p)=1,的时候存在(a*k)%p=1,即a有逆元k存在。
证明:

(a*k)%p=1 
=> a*k-p*y=1;
=> a*x-p*y=1;(将k转化为x)

  我们可以看到最后就是证明a * x - p * y = 1是否有整数解,我们可以知道当且仅当gcd(a,p)==1,该方程有解(用贝祖定理证明,具体这里不做证明了),那么我们可以很容易知道,该解可以用扩展欧几里得计算。
具体代码如下:

    #include
    #include
    #include
    #include
    using namespace std;
    const int mode=998244353;
    void ex_gcd(int a,int b,int&x,int&y){
     
         if(b==0){
     
             x=1;y=0;
             return;
         }
         ex_gcd(b,a%b,x,y);
         int tmp=x;
         x=y;
         y=x-(a/b)*y;
    }
    int solve(int a){
     
         int x,int y;
         ex_gcd(a,mode,x,y);
         x=(x%mode+mode)%mode;
         return x;
    }
    int main(){
     
         int a;
         scanf("%d",&a);
         int k=solve(a);
         printf("%d\n",k);
    }

2).用费马小定理求逆元(p为素数的时候)
  当p为素数,并且gcd(a,p)=1时,那么根据费马小定理,有a ^ (p-1)%p=1%p,即a*a ^ (p-2)%p=1%p,那么a的逆元就是a^(p-2);那么就是求a ^ (p-2)次就可以了,这里用快速幂来解。
代码如下:

#include
#include
#include
#include
using namespace std;
typedef long long LL;
const int mod=998244353;(mode要为质数)
LL quick(LL q,LL n){
     
    LL ans=1;
     while(n){
     
        if(n&1)ans=(ans*n)%mode;
           n<<=1;
           q=(q*q)%mode;
        }
        return ans;
}
int main(){
     
    int t;LL n;
    scanf("%d",&t);
    while(t--){
     
        scanf("%lld",&n);
        printf("%lld\n",quick(n,mode-2));
    }
}

阶乘逆元
  阶乘逆元是用来求0!到n!的逆元,如果只是每个元素求一遍,就很慢。(虽然用扩展欧几里得的时间可认为是常数)
  逆元可看为求倒数,那么

1/(1+n)!*(n+1)=1/n!(n-1)!*(n)*1/(n!)=1

即它们%p都是等于1的,所以(n-1)!的逆元为n*(1/n!);那么就很好求得n!的逆元了。
代码如下:

int inv(int b,int p){
     
   int a,k;
   ex_gcd(b,p,a,k);//扩展欧几里得
   if(a<0)a+=p;
   return a;
}
void init(){
     
   fact[0]=1;
   for(int i=1;i<=n;i++)fact[i]=fact[i-1]*i%mode;
   INV[n]=inv(fact[n],mode);  //求出n!逆元,需要用扩展欧几里得去算
   for(int i=n-1;i>=1;i++){
     
      inv[i]=fact[n]*inv[n]%mode;   //(n-1)!的逆元为n*(1/n!);
   }            
}

线性求逆元
  如果给你一道题,要求你算1到p-1关于p的逆元,如果p较大的时候,时间复杂度有点高。
  那么我们这时候应该怎么做呢?

我们可以假设p=k*i+r;
          =>k*i+r=0    (mod p)
          =>k*i*(i^(-1)*r^(-1))+r*(i^(-1)*r^(-1))=0 (mod p)
          =>k*r^(-1)+i^(-1)=0   (mod p)
          =>i^(-1)=-k*r^(-1)   (mod p)
          =>i^(-1)=-[(p-r)/i]*r^(-1)  (mod p)
          =>i^(-1)=-[p/i]*r^(-1)  (mod p)
          =>i^(-1)=-[p/i]*r^(-1)+p  (mod p)

代码如下:

int init(){
     
    inv[i]=1;
    for(int i=2;i<=n;i++)inv[i]=(p-p/i)*inv[p%i]%p;
    //p%i==r(因为我设p=k*i+r,p%i=k*i%i+r%i=r%i),inv[p%i]就是求r的逆元
    //r一定是小于i的,取模不可能大于模数。
}

最后非常感谢苏学长的帮助。

道阻且长
自己选的路 跪着也要走完。

你可能感兴趣的:(数学,算法)