学习数论的第四天之逆元

逆元
  什么是逆元?逆元就是(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<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    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 ) a ^ {(p-1)} a(p1)%p=1%p,即 a ∗ a ( p − 2 ) a*a ^ {(p-2)} aa(p2)%p=1%p,那么a的逆元就是 a ( p − 2 ) a^{(p-2)} a(p2);那么就是求 a ( p − 2 ) a ^ {(p-2)} a(p2)次就可以了,这里用快速幂来解。
代码如下:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
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*q)%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]=(i+1)*inv[i+1]%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的,取模不可能大于模数。
}

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

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

  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值