【数论相关】拓展欧几里得解线性同余方程和不定方程

前置知识:欧几里得算法(辗转相除法)

裴蜀定理

裴蜀定理(或贝祖定理,Bézout's identity)得名于法国数学家艾蒂安·裴蜀,说明了对任何整数a、b和它们的最大公约数d,关于未知数x和y的线性不定方程(称为裴蜀等式):若a,b是整数,且gcd(a,b)=d,那么对于任意的整数x,y,ax+by都一定是d的倍数,特别地,一定存在整数x,y,使ax+by=d成立。
它的一个重要推论是:a,b互质的充要条件是存在整数x,y使ax+by=1.

(来自百度百科)
相关证明可以查看百度百科的内容
百科链接

拓展欧几里得

顾名思义,就是欧几里得算法的拓展。可以在高速求出\(gcd(a,b)\)的同时,求出裴蜀定理中不定方程\(ax+by=gcd(a,b)\)的中\(x\)\(y\)的值。

下面是推导过程:

根据欧几里得算法可以得出\(gcd(a,b)=gcd(a,a \mod b)\)
由此可以列出下面两个方程
\(ax_0+by_0=gcd(a,b)\)
\(bx_1+(a \mod b) y_1=gcd(a,b)\)
因为\(a \mod b=a-a/b\)
所以原式可以写成:
\(bx_1+(a-a/b)y_1=gcd(a,b)\)
\(bx_1+ay_1+a/by_1=gcd(a,b)\)
\(ay_1+b(x_1-a/b*y_1)=gcd(a,b)\)
结合第一条方程,得出\(x_0=y_1,y_0=(x_1-a/b*y_1)\)
这样,拓展欧几里得的递归模式就显示出来了。
那么边界是什么呢?
显然是\(b=0\)时,此时\(a=gcd(a,b)\),显然有解\(x=1,y=0\)

练习

Luogu P1082
\(ax\equiv1\pmod {b}\)
对于这样一个同余方程,我们可以通过化为不定方程利用拓展欧几里得来求解
\(ax\equiv1\pmod {b}\)等价于\(ax\mod b=1 mod b\)
\(ax\mod b=1 mod b\)
可以得出\(b|ax-1\)(即\(ax-1\)\(b\)的倍数),不妨假设为-y倍,则原方程可以写成
\((ax-1)/b=-y\)
化简并移项整理得到\(ax+by=1\)
根据裴蜀定理,此时\(gcd(a,b)=1\)
所以我们就可以利用拓展欧几里得来求解了。

#include
using namespace std;
long long a,b,x,y;
void exgcd(int a,int b)
{
    if (b==0)
    {
        x=1;
        y=0;
        return ;
    }
    exgcd(b,a%b);
    int tmp=x;
    x=y;
    y=tmp-(a/b)*y;
}
int main()
{
    scanf("%lld%lld",&a,&b);
    exgcd(a,b);
    printf("%lld",(x%b+b)%b);//防止x出现负数的情况
    return 0;
}

乘法逆元

Luogu P3811

乘法逆元,是指数学领域群G中任意一个元素a,都在G中有唯一的逆元a‘,具有性质a×a'=a'×a=e,其中e为该群的单位元。

(来自百度百科)

百科的定义十分难懂,我们用比较容易理解的方式来讲述:

乘法逆元就是一个数在模\(p\)意义下的倒数。
\(a/b \mod p=k\)\(a*c\mod p=k\) 此时称\(c\)\(b\)在模\(p\)意义下的乘法逆元。
举个例子 \(3/3 \mod 11=1\)
\(3*4 \mod 11=1\)
4就是3在模11意义下的乘法逆元。
那么就可以根据倒数的性质,推出原数和乘法逆元的关系\(x*inv(x)\mod p=1\)
从而得到同余方程 \(x*inv(x)\equiv1\pmod {b}\)
于是就能够利用拓展欧几里得算法求解了。

#include
using namespace std;
long long p,n,x,y;
void exgcd(int a,int b)
{
    if (b==0)
    {
        x=1;
        y=0;
        return ;
    }
    exgcd(b,a%b);
    int tmp=x;
    x=y;
    y=tmp-(a/b)*y;
}
int main()
{
    scanf("%lld%lld",&n,&p);
    for (int i=1;i<=n;i++)
    {
        exgcd(i,p);
        printf("%lld\n",(x%p+p)%p);
    }
    return 0;
}

但是这种做法对于这道题只有80分。
因为数据量太大了……
于是又有神犇发明了线性求逆元的方式。
证明方式:不会

#include
using namespace std;
long long p,n,inv[20000530];
int main()
{
    scanf("%lld%lld",&n,&p);
    inv[1]=1;
    for (int i=2;i<=n;i++)
        inv[i]=-(p/i)*inv[p%i]%p;
    for (int i=1;i<=n;i++) printf("%lld\n",((inv[i]%p+p))%p);
    return 0;
}

推荐练习:

Luogu P2613
Luogu P1516

你可能感兴趣的:(【数论相关】拓展欧几里得解线性同余方程和不定方程)