扩展欧几里得算法求逆元_算法学习笔记(8):拓展欧几里得

acfb135b25ef1ebb2e60d4cca7f1073b.png

在了解拓展欧几里得算法之前,我们先来看看欧几里得算法(或辗转相除法),它的代码非常简单:

//辗转相除法,求两个数的最大公因数
int gcd(int a, int b)
{
    if (b == 0)
        return a;
    else
        return gcd(b, a%b);
}

它的过程大概是这样:

348bb8db5ff1f6b07f4b06b87ac9d41d.png

为什么辗转相除法是成立的?不妨设

(待会儿说明合理性),我们设
(相当于C++中的a/b
(相当于C++中的a%b
,那么:
  1. 都是
    的倍数。
  2. 那么
    也是
    的倍数。
  3. 那么
    也是
    的倍数。
  4. 由于
    ,所以
    的倍数。
  5. 所以
    的公因数。
  6. 由于
    ,所以
    的因数。

另一方面:

  1. 的倍数。
  2. 那么
    也是
    的倍数。
  3. 那么
    也是
    的倍数。
  4. 由于
    ,所以是
    的倍数。
  5. 所以
    的公因数。
  6. 由于
    ,所以
    的因数。

现在我们知道

的因数,
也是
的因数,这说明
,即
。所以只要不断地往下进行这个操作,就能求出两个数的最大公因数。

前面我提到“不妨设

”,出于这个顾虑,我过去常常会在这个函数开头加上这样一句代码:
if (a < b)
    swap(a, b);

后来我意识到,这是没有必要的。我们想想,如果

,会发生什么?
此时就等于
,所以函数会返回
,其实就完成了一次交换。

那么,什么是拓展欧几里得呢?这是一种算法,它可以在辗转相除途中求出不定方程

的一组解。

注意到上图中倒数第二行的

,可以改写为
,也就是说
可以被表示为
的线性组合。

然后倒数第三行,

,说明
可以被表示为
的线性组合,那么
也就可以被表示为
的线性组合。具体地,我们有:

这样一路推下去,即知

可以表示为
的线性组合。那么
就能找到解了。

我们知道,

的最大公因数。经过上面的流程,我们知道了求
的一种方法。那么如果
是其他的数,能不能找到解呢?实际上,
必须是
的倍数,因为我们由方程
两边除以
可得
,方程左边当然是整数,那么方程右边也必须是整数。所以
的倍数。

实际上,我们(不是很严谨地)证明了一个数论定理:

裴蜀定理

为正整数,则关于
的方程
有整数解当且仅当是
的倍数。

既然如此,我们只要求

的解就好了,对于方程
,只需要令
即可。

把刚刚提到过的流程画下来是这样的(右下角按规律添了两行):

a2082fd60662bd8d60ba3c9c80004c57.png

可以看到,通过求

的解,可以得出
的解。实际上,前者等价于
,也就是
,根据待定系数法可知:

和普通的辗转相除法相同,当

时递归结束,由上图右下角那个式子可知,此时应令

代码如下:

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

可以看到,确实就是在欧几里得算法的基础上进行了一些拓展。利用C++的引用特性,我们得以简洁地实现这一算法。注意这样解出的结果必然满足

,因为。

其实,还可以写得更简单一点。

int exgcd(int a, int b, int &x, int &y)
{
    if (b == 0)
    {
        x = 1;
        y = 0;
        return a;
    }
    int d = exgcd(b, a % b, y, x); //这里交换了x和y
    y -= (a / b) * x;
    return d;
}

有注释的那一行,交换了

,相当于令
。其实本质上没什么区别,只是书写和记忆起来更方便一点。

这样我们求得了

一组特解,那么 通解是什么呢?

设除了已经求出的

之外还有一组解
,那么由
,得
,得

但是,我们必须要保证

都是整数,后者等于
,其中
。由于
互质,
应当等于
是整数),即:

这便是该不定方程的通解。但其实,一般的题目不会让我们求通解,而会让我们求符合某些特殊条件的解,例如这道:

(洛谷P1082 同余方程)

题目描述
求关于
的同余方程
的最小正整数解。
输入格式
一行,包含两个正整数
,用一个空格隔开。
输出格式
一个正整数
,即最小正整数解。输入数据保证一定有解。

所谓

,其实就相当于
,移下项就是
。于是我们可以用拓展欧几里得算法求解(我们要求的是
,这里
还是
显然不重要)。注意有解的条件是
,即
互质,那么通解就是
。那么已知
要求最小正整数解,只需求
就好了,但因为C++对模的处理,如果
是负数,这里得到的是最大负整数解,所以需要再加上一个
。出于简化书写,可以统一加上b然后再模b。AC代码如下:
#include <bits/stdc++.h>
using namespace std;
int exgcd(int a, int b, int &x, int &y)
{
    if (b == 0)
    {
        x = 1;
        y = 0;
        return a;
    }
    int d = exgcd(b, a % b, y, x);
    y -= (a / b) * x;
    return d;
}
int main()
{
    int a, b, x, y;
    cin >> a >> b;
    exgcd(a, b, x, y);
    cout << ((x % b) + b) % b;
    return 0;
}

Pecco:算法学习笔记(目录)​zhuanlan.zhihu.com
5ed701be5a9c525a6a5b5585374d5bb8.png
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值