在了解拓展欧几里得算法之前,我们先来看看欧几里得算法(或辗转相除法),它的代码非常简单:
//辗转相除法,求两个数的最大公因数
int gcd(int a, int b)
{
if (b == 0)
return a;
else
return gcd(b, a%b);
}
它的过程大概是这样:
为什么辗转相除法是成立的?不妨设
(待会儿说明合理性),我们设
(相当于C++中的a/b
),
a%b
),
,
,那么:
-
和都是的倍数。
- 那么
也是的倍数。
- 那么
也是的倍数。
- 由于
,所以是的倍数。
- 所以
是和的公因数。
- 由于
,所以是的因数。
另一方面:
-
和是的倍数。
- 那么
也是的倍数。
- 那么
也是的倍数。
- 由于
,所以是的倍数。
- 所以
是和的公因数。
- 由于
,所以是的因数。
现在我们知道
是
的因数,
也是
的因数,这说明
,即
。所以只要不断地往下进行这个操作,就能求出两个数的最大公因数。
前面我提到“不妨设
”,出于这个顾虑,我过去常常会在这个函数开头加上这样一句代码:
if (a < b)
swap(a, b);
后来我意识到,这是没有必要的。我们想想,如果
,会发生什么?
此时就等于
,所以函数会返回
,其实就完成了一次交换。
那么,什么是拓展欧几里得呢?这是一种算法,它可以在辗转相除途中求出不定方程
的一组解。
注意到上图中倒数第二行的
,可以改写为
,也就是说
可以被表示为
和
的线性组合。
然后倒数第三行,
,说明
可以被表示为
和
的线性组合,那么
也就可以被表示为
和
的线性组合。具体地,我们有:
这样一路推下去,即知
可以表示为
和
的线性组合。那么
就能找到解了。
我们知道,
是
和
的最大公因数。经过上面的流程,我们知道了求
的一种方法。那么如果
是其他的数,能不能找到解呢?实际上,
必须是
的倍数,因为我们由方程
两边除以
可得
,方程左边当然是整数,那么方程右边也必须是整数。所以
是
的倍数。
实际上,我们(不是很严谨地)证明了一个数论定理:
(裴蜀定理)
设为正整数,则关于的方程有整数解当且仅当是的倍数。
既然如此,我们只要求
的解就好了,对于方程
,只需要令
即可。
把刚刚提到过的流程画下来是这样的(右下角按规律添了两行):
可以看到,通过求
的解,可以得出
的解。实际上,前者等价于
,也就是
,根据待定系数法可知:
和普通的辗转相除法相同,当
时递归结束,由上图右下角那个式子可知,此时应令
。
代码如下:
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