逆元详解

逆元:

建议先学的知识:

  1. 费马小定理
  2. 扩展欧几里得算法

逆元介绍及应用(inv):

求解公式(a/b)%m 时,因b可能会过大,会出现爆精度的情况,所以需变除法为乘法
设c是b的逆元,则有b*c≡1(mod m)
(a/b)%m = (a/b)*1%m = (a/b)*b*c%m = a*c(mod m);
即a/b的模等于a*b的逆元的模;

做题时可能用到的公式:

b*c≡1(mod m)
b≡c^(-1) (mod m)//逆元的逆元==数本身

求单点逆元方法

第一种:扩展欧几里得算法
时间复杂度:O(logn)

从b*c≡1(mod m)可以得出关系式 :k*m+1 = b*c->b*c+(-k)*m = 1完全符合扩展欧几里得算法

代码如下:

#include<iostream>
#include<cstdio>
#define D long long
using namespace std;

D exgcd(D a,D b,D &x,D &y){
    if(b==0){
        x=1;y=0;return a;
    }
    D d=exgcd(b,a%b,y,x);
    y-=a/b*x;
    return d;
}

D inv(D b,D m){//b*c≡1(mod m) -> b*c+m*k = 1
    D c,k;
    D d=exgcd(b,m,c,k);//因为b,m互质,所以d==1 ,r就是m,c/=d可省略 
    c/=d;
    D r=m/d;//r为多个解c1,c2,c3的差 
    c=(c%r+r)%r;
    return c; 
}


int main()
{
    D b,m;
    while(cin>>b>>m){
        cout<<inv(b,m)<<endl;
    } 
} 

第二种:费马小定理求解
时间复杂度:O(logn)

因为x^(p-1) ≡ 1(mod p),所以x * x^(p-2) ≡ 1 ( mod p ) ,即x的逆元为x^(p-2)注意:根据费马小定理,x与p互质时才可以用此方法。

代码如下:

#include<stdio.h>
#include<iostream>
#define D long long int
#define mod ((int)(1e9)+7)
using namespace std;

D swift(D a,D b)
{
    D base=a,mul=1;
    while(b)
    {
       if(b&1){mul=mul*base%mod;}
       base=base*base%mod;b>>=1;
    }
    return  mul;
}

D inv(D b,D m){
    return swift(b,m-2);
}

int main()
{
    D b,m;
    while(scanf("%lld%lld",&b,&m)!=EOF)
    {
        printf("%lld\n",inv(b,m));
    }
    return 0;
}

第三种:人眼求解
时间复杂度:O (10 / 脑细胞强度)

在当N是质数,a是(N+1)的约数时,inv [a] = ( N + 1 )/a
比如 mod 37 时,19的逆元为2

第四种:通用方法直接解题

在题目求a/b%mod时,一种方法是改成a*inv[b]%mod,即求除数的逆元;另一种办法是改成a%(mod*b)/b。此方法为通用方法,并不像费马小定理和扩展欧几里得算法求逆元一样需要a和mod互质。

例如:17/3%7=5 -> 17%(3*7)/3=5

证明:
这里写图片描述

线性递推求解逆元

即对inv[1]初始化为1,从1到n可直接由公式求得
递推公式为:inv[i] = ( m - m / i ) * inv[ m % i ] % m

分析:
假设 t = m / i , k = m % i ;
-> t * i + k ≡ 0 ( mod m )
-> - t * i ≡ k ( mod m )
-> - t * 1 / k ≡ 1 / i ( mod m )
因为 1 / i * i ≡ 1 ( mod m ),所以可以用inv[ i ] 代替 1 / i
-> - t * inv [ k ] ≡ inv [ i ] ( mod m )
再替换 t,k
-> inv [ i ] = ( m - m / i ) * inv [ m % i ] % m

代码如下:

#include<iostream>
#include<cstdio>
#include<cstring>
#define D long long
using namespace std;
#define N 100100
#define mmm(a,b) memset(a,b,sizeof(a))

D inv[N];
D m;  

void INinv(D m,D inv[],D n){
    mmm(inv,0);
    inv[1] = 1;  
    for(int i = 2; i <= n ; i++)  
    inv[i] = ( m - m / i ) * inv[ m % i ] % m;

}  
void OUTinv(D m,D inv[],D n){
    cout<<"when mod is "<<m<<endl;
    for(int i=1;i<=n;i++){
        cout<<inv[i]<<' ';
    }
    cout<<endl;
}

int main()
{
    D n=20;
    while(cin>>m){//m is mod which is prime
        INinv(m,inv,n);
        OUTinv(m,inv,n);
    }

}

逆元在阶乘运算上的应用

例如求组合数C(a,b) % mod
按照公式 C(a,b) = a! / b! / ( a - b )!
此时便可以运用逆元防止精度的问题,因为是%mod,所以乘法基本不会出错。

-> C(a,b) % mod = a! * inv [b!] * inv [( a - b )!] % mod

接下来就是求inv[i!]的事情了。

方法一:

i!= 1 * 2 * …. * i
∴ 理所当然只要拿1到 i 所有的数的逆元相乘便是 i!的逆元了
∴ inv [ i! ] = inv [1] * inv [2] * … * inv [i]

证明:inv [ i! ] * i! % mod = 1 = ( inv [1] * 1 % mod ) * ( inv [2] * 2 % mod ) * … * ( inv [i] * i % mod )

代码:

inv[1]=inv_fac[i]=1;
for(int i=2;i<N;i++){  
    inv[i]=(M-M/i)*inv[M%i]%M;  //递推求inv[i]
    inv_fac[i]=inv[i]*inv_fac[i-1]%M;  //inv_fac[i] 代表 inv[i!] 
}

方法二:

这个方法,本来想直接按照网上的公式写上去算了的,但是为了那些和我一样好学的人,也为了让自己的博客看起来不会不负责任,我看了好久,终于想明白了其中的道理。(可能是我笨才没有秒懂吧。。。)

这个方法首先需要根据费马小定理,得出inv_fac [n] = n! ^ (mod-2) ,然后前面的项即可用后面的项按公式推出。

公式还是挺6的,我自己默默推了一年。。。

-> inv [n!] = inv [( n - 1 )! * n]
看了第一种方法应该知道 inv [a * b] = inv [a] * inv [b]
-> inv [n!] = inv [(n - 1)!] * inv [n]
两边同时乘以 inv [inv [n]]即inv [n]的逆元,左边,因为逆元的逆元即是自身,即n,所以变为 inv [n!] * n;右边,在mod的情况下,inv [n] 乘以inv [n]的逆元为1,变为inv [(n - 1)!]
-> inv [(n - 1)!] = inv [n!] * n
-> inv_fac[i]=(inv_fac[i+1]*(i+1))%mod;

附上代码:

inv_fac[n]=swift(fac[n],mod-2);  //快速幂

for(int i=n-1;i>=1;i--)  
    inv_fac[i]=(inv_fac[i+1]*(i+1))%mod; 

例题

原题:xiaoxin juju needs help

题解:逆元在阶乘上的运用

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值