扩展欧几里得算法(详细推导+代码实现+应用)

1.扩展欧几里得算法

贝祖定理若a,b是整数,且gcd(a,b)=d,那么对于任意的整数x,y,ax+by=m中的m一定是d的倍数。(特别地,如果a、b是整数,那么一定存在整数x、y使得ax+by=gcd(a,b)。)

那么贝祖定理的一个直接应用就是:如果ax+by=1有解,那么gcd(a,b)=1(将原公式两边同时除以gcd(a,b))。

扩展欧几里得算法用来解决这样一个问题:给定两个非零的整数a和b,求一组整数解(x,y)使得ax+by = gcd(a,b)成立,其中gcd(a,b)表示a和b的最大公约数。(其中我们通过前面的贝祖定理可知解一定存在)

回忆我们知道的欧几里得算法,它总是把gcd(a,b)转化为求解gcd(b,a%b),而当b变为0时返回a,此时的a就等于gcd。也就是说,欧几里得算法结束时变量a中存放的是gcd,变量b中存放的是0,因此此时显然有 a × 1 + b × 0 = g c d a\times 1+b\times 0=gcd a×1+b×0=gcd成立,此时有x=1、y=0成立。

我们不妨利用上面的欧几里得算法的过程来计算x和y。目前已知的是递归边界成立时为x=1、y=0,需要想办法反推出最初始的x和y。

当计算gcd(a,b)时,有 a x 1 + b x 1 = g c d ax_1+bx_1=gcd ax1+bx1=gcd成立;

而在下一步计算gcd(b,a%b)时,又有 b x 2 + ( a % b ) y 2 = g c d bx_2+(a \%b)y_2=gcd bx2+(a%b)y2=gcd成立。

因此 a x 1 + b y 1 = b x 2 + ( a % b ) y 2 ax_1+by_1=bx_2+(a\%b)y_2 ax1+by1=bx2+(a%b)y2成立。

又考虑到有关系 a % b = a − ( a / b ) × b a\%b=a-(a/b)\times b a%b=a(a/b)×b成立(此处除法为整除),因此 a x 1 + b y 1 = a y 1 + b ( x 2 − ( a / b ) y 2 ) ax_1+by_1=ay_1+b(x_2-(a/b)y_2) ax1+by1=ay1+b(x2(a/b)y2)

对比等号左右两边可以马上得到下面的递推公式:

{ x 1 = y 2 y 1 = x 2 − ( a / b ) y 2 \begin{cases}x_1=y_2 \\ y_1=x_2-(a/b)y_2 \end{cases} {x1=y2y1=x2(a/b)y2

由此我们便可以通过 x 2 x_2 x2 y 2 y_2 y2来进行反推出 x 1 x_1 x1 y 1 y_1 y1了。代码如下:

int exGcd(int a, int b, int &x, int &y)   //x和y使用引用
{
    if(b == 0)
    {
        x = 1;
        y = 0;
        return a;
    }
    int g = exGcd(b, a%b, x, y);    //递归计算exGcd(b,a%b)
    int temp = x;             //存放x的值
    x = y;
    y = temp - (a/b)*y;       //更新y = x(old) - a/b*y(old)
    return g;                 //g是gcd
}

由于这里我们使用了引用,因此当exGcd函数结束时x和y就是所求的解。

显然,在得到这样一组解之后,就可以通过下面的式子得到全部解(其中K为任意整数):

{ x ′ = x + b g c d × K y ′ = y − a g c d × K \begin{cases}x'=x+\frac{b}{gcd}\times K \\ y'=y-\frac{a}{gcd}\times K \end{cases} {x=x+gcdb×Ky=ygcda×K

简单证明一下:

假设新的解为 x + s 1 x+s_1 x+s1 y − s 2 y-s_2 ys2,即有 a ∗ ( x + s 1 ) + b ∗ ( y − s 2 ) = g c d a*(x+s_1)+b*(y-s_2)=gcd a(x+s1)+b(ys2)=gcd成立,

通过代入 a x + b y = g c d ax+by=gcd ax+by=gcd可以得到 a s 1 = b s 2 as_1=bs_2 as1=bs2

于是 s 1 s 2 = b a \frac{s_1}{s_2}=\frac{b}{a} s2s1=ab成立。

为了让和 s 1 s_1 s1 s 2 s_2 s2尽可能小,可以让分子和分母同时除以一个尽可能大的数,同时保证它们仍然是整数。显然,由于 b g c d \frac{b}{gcd} gcdb a g c d \frac{a}{gcd} gcda互质,因此gcd是允许作为除数的最大数,于是 s 1 s 2 = b a = b / g c d a / g c d \frac{s_1}{s_2}=\frac{b}{a}=\frac{b/gcd}{a/gcd} s2s1=ab=a/gcdb/gcd

s 1 s_1 s1 s 2 s_2 s2的最小取值就是 b g c d \frac{b}{gcd} gcdb a g c d \frac{a}{gcd} gcda

证毕

也就是说,x和y的所有解分别以 b g c d \frac{b}{gcd} gcdb a g c d \frac{a}{gcd} gcda为周期。

那么其中x的最小非负整数解是什么呢?直观来说就是 x % b g c d x\%\frac{b}{gcd} x%gcdb。但是由于通过exGcd函数计算出来的x、y可以为负数,因此实际上 x % b g c d x\%\frac{b}{gcd} x%gcdb会得到一个负数,例如 ( − 15 ) (-15)%4=-3 (15)。考虑到即便x是负数, x % b g c d x\%\frac{b}{gcd} x%gcdb的范围也是在( − b g c d -\frac{b}{gcd} gcdb,0),因此对于任意整数来说, ( x % b g c d + b g c d ) % b g c d (x\%\frac{b}{gcd}+\frac{b}{gcd})\%\frac{b}{gcd} (x%gcdb+gcdb)%gcdb才是对应的最小非负整数解。

如果gcd=1,即ax+by=1时,全部解的公式简化为下式,且x的最小非负整数解也可以简化为 ( x % b + b ) % b (x\%b+b)\%b (x%b+b)%b

{ x ′ = x + b × K y ′ = y − a × K \begin{cases}x'=x+b\times K \\ y'=y-a\times K \end{cases} {x=x+b×Ky=ya×K

2.方程ax+by=c的求解

当我们知道了ax+by=gcd的解后,最主要的应用就是:求解ax+by=c,其中c为任意整数。

首先,假设ax+by=gcd有一组解 ( x 0 , y 0 ) (x_0,y_0) (x0,y0),现在在其等号的左右两边同时乘以 c g c d \frac{c}{gcd} gcdc

即有 a c x 0 g c d + b c y 0 g c d = c a\frac{cx_0}{gcd}+b\frac{cy_0}{gcd}=c agcdcx0+bgcdcy0=c成立,因此 ( x , y ) = ( c x 0 g c d , c y 0 g c d ) (x,y)=(\frac{cx_0}{gcd},\frac{cy_0}{gcd}) (x,y)=(gcdcx0,gcdcy0)是ax+by=c的一组解。

但是显然这样做的充要条件是c%gcd==0(因为: a x g c d + b y g c d = c g c d a\frac{x}{gcd}+b\frac{y}{gcd}=\frac{c}{gcd} agcdx+bgcdy=gcdc,其中左边为整数因此右边也为整数)

否则第一步将无法做到。

为了获取全部解的公式,我们可以模仿前面的做法,假设新的解为 x + s 1 x+s_1 x+s1 y − s 2 y-s_2 ys2,然后将 a ∗ ( x + s 1 ) + b ∗ ( y − s 2 ) = c a*(x+s_1)+b*(y-s_2)=c a(x+s1)+b(ys2)=c a x + b y = c ax+by=c ax+by=c联立,发现同样可得 s 1 s 2 = b a \frac{s_1}{s_2}=\frac{b}{a} s2s1=ab成立。于是因为同样的原因, s 1 s_1 s1 s 2 s_2 s2的最小取值仍然是 b g c d \frac{b}{gcd} gcdb a g c d \frac{a}{gcd} gcda。因此ax+by=c的全部解的公式为:

{ x ′ = x + b g c d ∗ K = c x 0 g c d + b g c d ∗ K y ′ = y − a g c d ∗ K = c y 0 g c d − a g c d ∗ K \begin{cases}x'=x+\frac{b}{gcd}*K=\frac{cx_0}{gcd}+\frac{b}{gcd}*K \\ y'=y-\frac{a}{gcd}*K=\frac{cy_0}{gcd}-\frac{a}{gcd}*K \end{cases} {x=x+gcdbK=gcdcx0+gcdbKy=ygcdaK=gcdcy0gcdaK

我们发现,这与ax+by=gcd全部解的公式是一样的,唯一不同的是初始解(x,y)不同。因此,对于ax+by=c来说,其解同样分别以 b g c d \frac{b}{gcd} gcdb a g c d \frac{a}{gcd} gcda为周期。

但是我们要注意的是:在ax+by=gcd的全部解的基础上都乘以 c g c d \frac{c}{gcd} gcdc只能获取一部分解。

这是因为:

由于 c ≥ g c d c\geq gcd cgcd(根据c%gcd==0得到),因此ax+by=gcd的全部解乘以 c g c d \frac{c}{gcd} gcdc会导致周期放大为原先的 c g c d \frac{c}{gcd} gcdc倍(x和y的周期会边为 b c g c d 2 \frac{bc}{gcd^2} gcd2bc a c g c d 2 \frac{ac}{gcd^2} gcd2ac),所以导致漏解。

3.同余式 a x ≡ c ( m o d   m ) ax\equiv c(mod\ m) axc(mod m)的解

既然已经解决了ax+by=c,不得不提的就是同余式 a x ≡ c ( m o d   m ) ax\equiv c(mod\ m) axc(mod m)的求解。

什么式同余式:对于a、b、m来说,如果m整除a-b(即(a-b)%m=0),那么就说a与b模m同余,对应的同余式为 a x ≡ c ( m o d   m ) ax\equiv c(mod\ m) axc(mod m),m称为同余式的模。

那么我们解决同余式 a x ≡ c ( m o d   m ) ax\equiv c(mod\ m) axc(mod m)的解呢?

根据同余式的定义,有 ( a x − c ) % m = 0 (ax-c)\%m=0 (axc)%m=0成立

因此存在整数y,使 a x − c = m y ax-c=my axc=my成立

移项并令y=-y后即得 a x + m y = c ax+my=c ax+my=c

由前面的结论我们知道,当 c % g c d ( a , m ) = = 0 c\%gcd(a,m)==0 c%gcd(a,m)==0时方程才有解,且解的形式如下,

其中(x,y)是ax+my=c的其中一组解,可以先通过求解ax+my=gcd(a,m)得到 ( x 0 , y 0 ) (x_0,y_0) (x0,y0),然后由公式 ( x , y ) = ( c x 0 g c d ( a , m ) , c y 0 g c d ( a , m ) ) (x,y)=(\frac{cx_0}{gcd(a,m)},\frac{cy_0}{gcd(a,m)}) (x,y)=(gcd(a,m)cx0,gcd(a,m)cy0)直接得到。

{ x ′ = x + m g c d ( a , m ) × K y ′ = y − a g c d ( a , m ) × K ( K 为 任 意 整 数 ) \begin{cases}x'=x+\frac{m}{gcd(a,m)}\times K \\ y'=y-\frac{a}{gcd(a,m)}\times K \end{cases} (K为任意整数) {x=x+gcd(a,m)m×Ky=ygcd(a,m)a×KK

虽然对方程ax+my=c来说,K可以取任意整数,但是对同余式来说会有很多解在模m意义下是相同的(由于只关心x,因此下面只考虑x)。对同余式来说没只需要找出那些在模m意义下不同的解。因此考虑 x ′ = x + m g c d ( a , m ) ∗ K x'=x+\frac{m}{gcd(a,m)}*K x=x+gcd(a,m)mK,会发现当K分别取0、1、2、…、gcd(a,m)-1时,所得到的解在模m意义下是不同的,而其他解都可以对应到K取这gcd(a,m)个数值之一。由此可得到结论:

设a,c,m是整数,其中 m ≥ 1 m\geq 1 m1,则

①若 c % g c d ( a , m ) ≠ 0 c\%gcd(a,m)\neq0 c%gcd(a,m)=0,则同余式方程 a x ≡ c ( m o d   m ) ax\equiv c(mod\ m) axc(mod m)无解。

②若 c % g c d ( a , m ) = 0 c\%gcd(a,m)=0 c%gcd(a,m)=0,则同余式方程 a x ≡ c ( m o d   m ) ax\equiv c(mod\ m) axc(mod m)恰好由gcd(a,m)个模m意义下不同的解,且解的形式为:

x ′ = x + m g c d ( a , m ) ∗ K x'=x+\frac{m}{gcd(a,m)}*K x=x+gcd(a,m)mK

4.逆元的求解以及(b/a)%m的计算

接着我们来解决最后一个问题,假设a、m是整数,求a模m的逆元

什么是逆元(此处特指乘法的逆元):

假设a、b、m是整数,m>1,且有 a b ≡ 1 ( m o d   m ) ab\equiv1(mod\ m) ab1(mod m)成立,

那么就说a和b互为模m的逆元,一般记作 a ≡ 1 b a\equiv \frac{1}{b} ab1 b ≡ 1 a b\equiv \frac{1}{a} ba1

通俗的讲就是,如果两个整数的乘积模m后等于1,那么就称它们互为m的逆元

那么逆元有什么用处呢?

对乘法来说有 ( b ∗ a ) % m = ( ( b % m ) ∗ ( a % m ) ) % m (b*a)\%m=((b\%m)*(a\%m))\%m (ba)%m=((b%m)(a%m))%m成立,但是对除法来说, ( b / a ) % m = ( ( b % m ) / ( a % m ) ) % m (b/a)\%m=((b\%m)/(a\%m))\%m (b/a)%m=((b%m)/(a%m))%m却不成立, ( b / a ) % m = ( ( b % m ) / a ) % m (b/a)\%m=((b\%m)/a)\%m (b/a)%m=((b%m)/a)%m也不成立,

例如:如果要对12/4对2取模,采用((12%2)/4)%2会得到错误的结果0,而实际上结果是1。

这时就需要逆元来计算(b/a)%m:

通过找到a模m的逆元x,就有(b/a)%m=(b*x)%m成立(只考虑整数取模,也即假设b%a=0,即b是a的整数倍),于是就把除法取模转化为乘法取模,这对于解决被除数b非常大(使得b已经取过模,不是原始值)的问题来说是非常实用的。

由定义知,求a模m的逆元,就是求解同余式 a x ≡ 1 ( m o d   m ) ax\equiv1(mod\ m) ax1(mod m),并且在实际使用中,一般把x的最小正整数解称为a模m的逆元,因此下文中提到的逆元都默认为x的最小正整数解。显然,同余式 a x ≡ 1 ( m o d   m ) ax\equiv1(mod\ m) ax1(mod m)是否有解取决于1%gcd(a,m)是否为0,而这等价于gcd(a,m)是否为1:

①如果 g c d ( a , m ) ≠ 1 gcd(a,m)\neq1 gcd(a,m)=1,那么同余式 a x ≡ 1 ( m o d   m ) ax\equiv1(mod\ m) ax1(mod m)无解,a不存在模m的逆元。

②如果 g c d ( a , m ) = 1 gcd(a,m)=1 gcd(a,m)=1,那么同余式 a x ≡ 1 ( m o d   m ) ax\equiv1(mod\ m) ax1(mod m)在(0,m)上有唯一解,可以通过求解ax+my=1得到。

注意:由于gcd(a,m)=1,因此ax+my=1=gcd(a,m),直接使用扩展欧几里得算法解出x之后就可以用 ( x % m + m ) (x\%m+m)%m (x%m+m)得到(0,m)范围内的解,也就是所需要的逆元。

下面代码使用了扩展欧几里得算法来求解a模m的逆元,使用条件是gcd(a,m)=1,当然如果m是素数,就肯定成立了,可以放心使用。

int inverse(int a,int m)
{
	int x, y;
	int g = exGcd(a, m, x, y);   //求解ax+my=1
    return (x%m+m)%m;            //a模m的逆元为(x%m+m)%m
}

补充说明:

如果m是素数,且a不是m的倍数,则还可以直接使用费马小定理来得到逆元,这种做法不需要使用扩展欧几里得算法。

费马小定理:设m是素数,a是任意整数且a不能整除m,则 a m − 1 ≡ 1 ( m o d m ) a^{m-1}\equiv1\pmod m am11(modm)

使用费马小定理来推到逆元的方法非常简单:由 a m − 1 ≡ 1 ( m o d m ) a^{m-1}\equiv1\pmod m am11(modm)可知 a ∗ a m − 2 ≡ 1 ( m o d m ) a*a^{m-2}\equiv1\pmod m aam21(modm),直接由逆元的定义便可以知道, a m − 2 % m a^{m-2}\%m am2%m就是a模m的逆元,而这可以通过快速幂的方法来求解。

扩展欧几里得算法可以求解形如 ax + by = gcd(a, b) 的一次不定方程的整数解,其中 a,b 是任意两个整数,gcd(a,b) 表示 a 和 b 的最大公约数。 对于求解乘法逆元,我们需要解的方程形式为 ax + by = 1,其中 a 是需要求逆的数,b 是取模的数(通常为一个质数),x 就是 a 的逆元。因此,我们只需将式子中的 1 替换成 1 % b,即可得到 ax + by = 1 % b 的形式。 具体求解的步骤如下: 1. 对于给定的 a 和 b,使用欧几里得算法求得它们的最大公约数 gcd(a, b),并记录每一步的余数和系数。 2. 从最后一步开始,倒序向上推导,求得 x 的值。设最后一步的余数为 r,系数为 k,则有 r = k * a + (b % a),因此有 r - k * a = b % a。注意这里要使用 b % a 而不是 r % a,因为 b 作为模数,是固定不变的。 3. 将上一步中求得的 x 带入前面的等式中,逐步向上推导,求得 y 的值。 4. 最终得到 x 就是 a 在模数 b 下的逆元。 下面是一个示例代码: ```python def ext_gcd(a, b): if b == 0: return a, 1, 0 else: gcd, x, y = ext_gcd(b, a % b) return gcd, y, x - (a // b) * y def mod_inv(a, b): gcd, x, y = ext_gcd(a, b) if gcd != 1: raise ValueError("a is not invertible mod b") else: return x % b ``` 其中,ext_gcd 函数实现扩展欧几里得算法,mod_inv 函数则直接调用 ext_gcd 求解 a 在模数 b 下的逆元。注意,如果 a 在模数 b 下没有逆元,则会抛出 ValueError 异常。
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

胡小涛

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值