如何利用扩展欧几里得算法求解不定方程_扩展欧几里得算法

46e95f1b34723481d847e1b97eb7ecbc.png

乘法逆元

在中国剩余定理的计算里,需要求一个数字在一个模下的逆元,也就是对于给定的 a,b,找到方程

equation?tex=a+a%5E%2A+%5Cequiv+1+%5Cpmod%7Bb%7D 的一个整数解
equation?tex=a%5E%2A 。接下来我们分析一下这个方程背后隐藏着什么。

根据同余的定义,有

equation?tex=b+%5Cmid+%28a+a%5E%2A+-+1%29 ,也就是存在整数
equation?tex=k 使得
equation?tex=bk%3Da+a%5E%2A-1 。移一下项,就得到了
equation?tex=a+a%5E%2A-bk%3D1

这个形式恰好符合裴蜀定理

equation?tex=ax%2Bby%3D1 的形式,于是
equation?tex=%28a%2Cb%29%3D1 ,这表明
a,b互质是逆元存在的必要条件。同样可以证明: a,b互质是 a 在模 b 下存在逆元的充分条件

欧几里得算法

从上面的论述可以看出,要求一个数的逆元,实际上就是找到裴蜀定理

equation?tex=ax%2Bby%3D%28a%2Cb%29%3D1 中的 x 和 y。实际上这一步工作可以通过辗转相除法完成。

碾转相除法的依据是一个基本的事实:

equation?tex=%28a%2Cb%29%3D%28a-kb%2Cb%29%2Ck+%5Cin+%5Cmathbb%7BZ%7D

这样,在 a > b时,令

equation?tex=k%3D%5Clfloor+%5Cfrac%7Ba%7D%7Bb%7D+%5Crfloor ,我们得到
equation?tex=%28a%2Cb%29%3D%28a-b%5Clfloor+%5Cfrac%7Ba%7D%7Bb%7D+%5Crfloor%2Cb%29%3D%28a%5Cbmod+b%2Cb%29%3D%28b%2C+a+%5Cbmod+b%29 。注意到
equation?tex=b+%3E+%28a+%5Cbmod+b%29 ,所以这个操作可以继续进行下去,直到 b=0,此时
equation?tex=%28a%2C0%29%3Da

这种求最大公约数的算法叫做辗转相除法,又叫欧几里得算法。

根据上面的描述,很容易得出一个碾转相除法的递归实现:

int gcd(int a, int b) {
    if(a < b) return gcd(b, a);
    if(b == 0) return a;
    return gcd(b, a % b);
}

对这个函数进行尾递归内联优化,就得到了它的迭代版本:

int gcd(int a, int b) {
    if(a < b) return gcd(b, a);
    while(b != 0) {
        int t = a % b;
        a = b; b = t;
    }
    return a;
}

辗转相除法是计算最大公约数很快的一种方式,但是它的潜力远不止此。下面我们要用它完成更加神奇的事情。


扩展欧几里得算法

欧几里得算法的另一个用途就是求解

equation?tex=%28a%2Cb%29%3Dax%2Bby 中的 x 和 y。

在欧几里得算法里,递归部分的核心是这样的:

equation?tex=%28a%2Cb%29%3D%28b%2C+a+%5Cbmod+b%29%3D%28b%2C+a-b%5Clfloor+%5Cfrac%7Ba%7D%7Bb%7D+%5Crfloor%29 ,我们将由此得出与 x,y 相关的递归关系。

根据裴蜀定理,我们可以找到四个整数

equation?tex=x%2Cy%2Cx%27%2Cy%27 ,使得
equation?tex=ax%2Bby%3D%28a%2Cb%29
equation?tex=bx%27%2B+%28a-b%5Clfloor+%5Cfrac%7Ba%7D%7Bb%7D+%5Crfloor%29y%27%3D%28b%2C+a-b%5Clfloor+%5Cfrac%7Ba%7D%7Bb%7D+%5Crfloor%29

这两个等式的右侧是相等的,于是我们得到:

equation?tex=ax%2Bby%3Dbx%27%2B+%28a-b%5Clfloor+%5Cfrac%7Ba%7D%7Bb%7D+%5Crfloor%29y%27 ,整理一下就是
equation?tex=a%28x-y%27%29%2Bb%28y-%28x%27-%5Clfloor+%5Cfrac%7Ba%7D%7Bb%7D+%5Crfloor+y%27%29%29%3D0

我们希望这个等式对一切 a,b 都成立,于是

equation?tex=x%3Dy%27
equation?tex=y%3Dx%27-%5Clfloor+%5Cfrac%7Ba%7D%7Bb%7D+%5Crfloor+y%27

这样,要求解 x 和 y,只需要求解 x',y',由于 x',y' 对应的问题规模更小,所以可以进行递归的运算。最后找一下边界条件:当 b=0 时,

equation?tex=%28a%2C0%29 对应
equation?tex=x%3D1
equation?tex=y%3D0

这种算法的思想和欧几里得算法是一致的,而且它在求出最大公约数的同时,求解了一组适于裴蜀定理的系数,所以叫做扩展欧几里得算法。

递归算法也是很好写的:

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

x 和 y 用引用的形式传入,返回值是最大公约数。


扩展欧几里得算法的迭代形式

有一个不太令人高兴的事实:扩展欧几里得算法的递归是在函数中央的,不能进行尾递归内联。

有句古话说得好:一时迭代一时爽,一直迭代一直爽。

36db32e9a8bb83aa80d2962ea39e637b.png
鲁迅:这锅我不背

这点困难怎么能阻挡我们寻找迭代算法的脚步!

注意到,递归的每一步都是关于 x',y' 的线性组合,所以我们可以考虑用类似于线性递推数列的方式,用矩阵的形式改写递归式,从而利用矩阵乘法的结合律找到迭代算法

这是我们找到的递归式:

equation?tex=%5Cbegin%7Bcases%7D+x%3Dy%27%5C%5C%5B2ex%5D+y%3Dx%27-%5Clfloor+%5Cfrac%7Ba%7D%7Bb%7D+%5Crfloor+y%27+%5Cend%7Bcases%7D

然后变成矩阵形式:

equation?tex=%5Cbegin%7Bpmatrix%7Dx%5C%5Cy%5Cend%7Bpmatrix%7D%3D%5Cbegin%7Bpmatrix%7D0%261%5C%5C1%26-d_1%5Cend%7Bpmatrix%7D%5Cbegin%7Bpmatrix%7Dx%27%5C%5Cy%27%5Cend%7Bpmatrix%7D

这里

equation?tex=d_1 代表第一次迭代时的
equation?tex=%5Clfloor+%5Cfrac%7Ba%7D%7Bb%7D+%5Crfloor

然后就可以一直乘下去:

equation?tex=%5Cbegin%7Bpmatrix%7Dx%5C%5Cy%5Cend%7Bpmatrix%7D%3D%5Cbegin%7Bpmatrix%7D0%261%5C%5C1%26-d_1%5Cend%7Bpmatrix%7D%5Cbegin%7Bpmatrix%7D0%261%5C%5C1%26-d_2%5Cend%7Bpmatrix%7D%5Cbegin%7Bpmatrix%7D0%261%5C%5C1%26-d_3%5Cend%7Bpmatrix%7D%5Ccdots%5Cbegin%7Bpmatrix%7D0%261%5C%5C1%26-d_n%5Cend%7Bpmatrix%7D%5Cbegin%7Bpmatrix%7D1%5C%5C0%5Cend%7Bpmatrix%7D

在每次迭代时,都可以从左向右计算一个矩阵,这样就达到了迭代的目的

假设在一次迭代之前计算的矩阵之积为

equation?tex=%5Cbegin%7Bpmatrix%7Dm_1%26m_2%5C%5Cn_1%26n_2%5Cend%7Bpmatrix%7D ,然后下一个矩阵
equation?tex=%5Cbegin%7Bpmatrix%7D0%261%5C%5C1%26-d_t%5Cend%7Bpmatrix%7D 是右乘上去的:

equation?tex=%5Cbegin%7Bpmatrix%7Dm_1%26m_2%5C%5Cn_1%26n_2%5Cend%7Bpmatrix%7D%5Cbegin%7Bpmatrix%7D0%261%5C%5C1%26-d_1%5Cend%7Bpmatrix%7D%3D%5Cbegin%7Bpmatrix%7Dm_2%26m_1-d_t+m_2%5C%5Cn_2%26n_1-d_t+n_2%5Cend%7Bpmatrix%7D

把所有矩阵的积计算完成之后,得到一个最终的矩阵

equation?tex=%5Cbegin%7Bpmatrix%7DM_1%26M_2%5C%5CN_1%26N_2%5Cend%7Bpmatrix%7D ,这个矩阵要满足
equation?tex=%5Cbegin%7Bpmatrix%7Dx%5C%5Cy%5Cend%7Bpmatrix%7D%5Cbegin%7Bpmatrix%7DM_1%26M_2%5C%5CN_1%26N_2%5Cend%7Bpmatrix%7D%3D%5Cbegin%7Bpmatrix%7D1%5C%5C0%5Cend%7Bpmatrix%7D ,展开整理,就得到
equation?tex=x%3DM_1
equation?tex=y%3DN_1

再看一看初始值,就是单位阵

equation?tex=%5Cbegin%7Bpmatrix%7Dm_1%26m_2%5C%5Cn_1%26n_2%5Cend%7Bpmatrix%7D%3D%5Cbegin%7Bpmatrix%7D1%260%5C%5C0%261%5Cend%7Bpmatrix%7D

所以这里可以有一个小(负)优化,在迭代时直接把矩阵写成

equation?tex=%5Cbegin%7Bpmatrix%7Dx%26m_2%5C%5Cy%26n_2%5Cend%7Bpmatrix%7D ,这样迭代完成之后 x 和 y 的值就计算完了。

现在梳理一下思路:

  1. 第一步是给矩阵赋初始值。
    equation?tex=%5Cbegin%7Bpmatrix%7Dx%26m%5C%5Cy%26n%5Cend%7Bpmatrix%7D%3D%5Cbegin%7Bpmatrix%7D1%260%5C%5C0%261%5Cend%7Bpmatrix%7D
  2. 然后开始迭代。每一次迭代时,计算
    equation?tex=d+%3D+%5Clfloor+%5Cfrac%7Ba%7D%7Bb%7D+%5Crfloor
  3. 更新矩阵,把矩阵右乘迭代矩阵的结果赋值给原先的矩阵。
    equation?tex=%5Cbegin%7Bpmatrix%7Dx%26m%5C%5Cy%26n%5Cend%7Bpmatrix%7D%5Cleftarrow%5Cbegin%7Bpmatrix%7Dx%26m%5C%5Cy%26n%5Cend%7Bpmatrix%7D%5Cbegin%7Bpmatrix%7D0%261%5C%5C1%26-d%5Cend%7Bpmatrix%7D%3D%5Cbegin%7Bpmatrix%7Dm%26x-d+m%5C%5Cn%26y-d+n%5Cend%7Bpmatrix%7D
  4. 进行一般欧几里得算法的迭代。
    equation?tex=%5Cbegin%7Bpmatrix%7Da%5C%5Cb%5Cend%7Bpmatrix%7D%5Cleftarrow%5Cbegin%7Bpmatrix%7Db%5C%5Ca%5Cbmod+b%5Cend%7Bpmatrix%7D
  5. 当 b = 0 时,计算结束,此时 x 和 y 已经计算完成,并且最大公约数储存在 a 中。

给一个迭代版本的代码:

int exgcd(int a, int b, int& x, int& y) {
    if(a < b) return exgcd(b, a, y, x);
    int m = 0, n = 1;
    x = 1; y = 0;
    while(b != 0) {
        int d = a / b, t;
        t = m; m = x - d * t; x = t;
        t = n; n = y - d * t; y = t;
        t = a % b; a = b; b = t;
    }
    return a;
}

扩展欧几里得算法与乘法逆元

当我们用扩展欧几里得算法找到一组 x 和 y 满足

equation?tex=ax%2Bby%3D1 时,就可以得出
equation?tex=ax%5Cequiv+1+%5Cpmod%7Bb%7D ,也就是说 x 是 a 的乘法逆元。

显然,如果 x 是 a 的乘法逆元,那么所有的

equation?tex=x%2Bkb%2C+k+%5Cin+%5Cmathbb%7BN%7D 也是 a 的乘法逆元。这表明,一定有 a 的一个乘法逆元在区间
equation?tex=%5B0%2Cb%29 内。

这是找到 n 在模 p 下的乘法逆元的一个算法,使用了上面的 exgcd:

int inv(int n, int p) {
    int x, y;
    if(exgcd(n, p, x, y) == 1) {
        x = x % p;
        return x >= 0 ? x : p + x;
    } else {
        return -1;
    }
}

如果返回 -1,表示乘法逆元不存在。


(封面pid:72665414)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值