关于逆元、除法取模

逆元的引入

在做组合数取模的时候,常常要求\((a/b)\%p^{[1]}\),然而不同于加减乘,除法取模没有\((a/b)\%p=(a\%p/b\%p)\%p\)的性质。因此,我们引入乘法逆元的概念。

\(a* b≡1(\%p)\),则设\(b=inv(a)\),称\(b\)\(a\)的逆元,满足\((a* b)\%p=(a\%p* b\%p)\%p\)。这样,除法转换为乘法,就可以了。但前提是对于两个整数\(A,B\),要计算\(A/B\)时,\(A\)必须能够被\(B\)整除。

对于两个取模过的数,逆元是怎么输出商的?它实际上是在对给定的取模过的数\(a,b\),找最小的两个\(A,B\),满足\(A=x* p+a,B=y* p+b,A\%B=0\),并返回\(A/B\)

求逆元的几种常用方法

1. 扩展欧几里得

欧几里得定理

想必都知道\[gcd(a,b)=gcd(b,a\%b)\]

解关于\(x,y\)的方程\(ax+by=gcd(a,b)\)

首先,我们先解出方程的一个特解,再通过特解推出其余的通解即可。

如何求特解

我们来看下面两个方程:
\[ax_1+by_1=gcd(a,b)\]
\[bx_2+(a\%b)y_2=gcd(b,a\%b)\]
\(\because gcd(a,b)=gcd(b,a\%b),\)

\(\therefore ax_1+by_1=bx_2+(a\%b)y_2\)

\(\because a\%b=a-floor(a/b)* b^{[2]}\)

\(\therefore ax_1+by_1=ay_2+b(x_2-floor(a/b)* y_2)\)

由等式两边的系数关系得\[x_1=y_2\]\[y_1=x_2-floor(a/b)* y_2\]
于是形成了一种递归关系,可用递归解决。

递归终止状态

我们知道在计算最大公约数的过程中,\(b\)最终会等于\(0\),此时\(a=gcd(A,B)\),因此,这时方程化为\(a* x=gcd(A,B)\),解得\(x=1,y=0\)

如何求通解

求得一组特解之后,我们发现,对于方程\(ax+by=gcd(a,b)\),当\(x\)增加\(b/gcd\)\(y\)减少\(a/gcd\)时,等式仍成立。

证明

\(ax\)项增加\(a* b/gcd\)\(by\)项减少\(a* b/gcd\)

于是可求得所有通解。

如何求最小非负整数解

此处以\(x\)为例,\(y\)同理。我们让\(x\)一直减\(B/gcd\)直至求出最小解,即让\(x\%(B/gcd)\)即可。

解关于\(x,y\)的方程\(Ax+By=C\)

我们发现,当\(C\)不能整除\(gcd(A,B)\)时,方程就不能化为\(ax+by=gcd(a,b)\)的形式,因此就无解。否则有无数解,将方程两边同时除以\(gcd(A,B)\)求解即可。

利用扩展欧几里得求乘法逆元

因为\[a* x≡1(\%p)\]
所以\[a* x+b* y=1\]
此时,若\(1\%gcd(a,b)≠1\),即\(gcd(a,b)≠1\),则该方程无解。
否则,设特解为\(x_0\),最小非负整数解\(x=x_0\%m\)。当\(m<0\)时,由于计算机取模一个负数与数学上的意义不同,所以应把模数取绝对值。当\(x_0<0\)时,取模的结果是一个负数,所以把答案加上\(m\)即可。

复杂度为\(\log\)级。

Code

int egcd(int a,int b,int& x,int& y){
//x,y返回方程ax+by=gcd(a,b)的一组特解,函数返回gcd(a,b)
    if(b==0){
        x=1;
        y=0;
        return a;
//递归终止条件
    }
    int ans=egcd(b,a%b,x,y);
    int tmp=x;
    x=y;
    y=tmp-a/b*y;
//x2,y2->x1,y1
    return ans;
}
int cal(int a,int m){
//求a*x≡1(%m)的x(逆元)
    int x,y;
    int g=egcd(a,m,x,y);
//求a*x+b*y=1的特解,g=gcd(a,b)
    if(g!=1)return -1;
//无解(即1%g!=0)
    m=abs(m);
//负数特判
    int ans=x%m;
//求最小非负整数解
    if(ans<=0)ans+=m;
//负数特判
    return ans;
}

2. 费马小定理

\(p\)为质数时,对\(a* x≡1(\%p)\)\(x=a^{p-2}\%p\)

复杂度为\(\log\)级。

Code

typedef long long D;
D qpow(D x,D y,D p){
    D ans=1;
    while(y){
        if(y&1)ans=ans*x%p;
        x=x*x%p;
        y>>=1;
    }
    return ans;
}
D cal(D a,D p){
    return qpow(a,p-2,p);
}

组合数取模

由于题目中给的\(p\)一般都是质数,所以下面我们默认\(p\)为质数

1. 错误方法

由上面的结论,我们得出\[C(n,m)=\frac{n!}{m!(n-m)!}=n!* inv(m!)* inv((n-m)!)\]
直接使用扩展欧几里得或费马小定理求逆元来实现除法取模。

此方法的错误之处在于,对\(a* x≡1(\%p),a\)有逆元的前提条件时\(a,p\)互质。当\(a,p\)不互质,即\(a\)\(p\)的倍数时,不存在逆元,也就无法计算。

比如计算\(C(7,5)\%5\),正确答案应该是\(\frac{7* 6}{1* 2}\%5=21\%5=1\),但计算过程中出现了\(5\)的倍数,取模\(5\)之后就变为\(0\),因此最后答案输出了\(0\)

2. lucas定理

lucas定理可避免上述情况。

递归式:

\(lucas(n,m,p)\)\(C(n,m)\%p\)\(p\)为质数),则
\[lucas(n,m,p)=C(n\%p,m\%p,p)* lucas(n/p,m/p,p)\]
此处的\(C(n,m,p)\)可直接通过逆元计算。

复杂度为\(\log\)级。

Code

typedef long long D;
D fac[100001];
void preparefac(D n,D p){
    fac[0]=1;
    for(D i=1;i<=n;i++){
        fac[i]=i*fac[i-1]%p;
    }
}
D qpow(D x,D y,D p){
    D ans=1;
    while(y>0){
        if(y%2)ans=ans*x%p;
        x=x*x%p;
        y/=2;
    }
    return ans;
}
D cal(D x,D y){
    return qpow(x,y-2,y);
}
D Div(D x,D y,D p){
    return x*cal(y,p)%p;
}
D C_(D n,D m,D p){
    if(m>n)return 0;
    return Div(Div(fac[n],fac[m],p),fac[n-m],p);
}
D C(D n,D m,D p){
    if(!m)return 1;
    return C_(n%p,m%p,p)*C(n/p,m/p,p)%p;
}

注:
[1] 在本文中,\(\%\)表示取模运算,等价于\(mod\)
[2] \(floor(x)\)表示\(x\)向下取整。

猜你喜欢

转载自www.cnblogs.com/BlogOfchc1234567890/p/9861530.html