拓展欧几里得算法求解二元一次不定方程
本节内容,主要围绕 为什么、怎么做、怎么写 这三个关系来讲解。
为什么?
为什么拓展欧几里得算法可以求二元一次不定方程的解?
首先明确:对于任意的 a x + b y = c ax+by=c ax+by=c都可以转化为 a x + b y = g c d ( a , b ) ax+by=gcd(a,b) ax+by=gcd(a,b)的求解。
所以只需要关注: a x + b y = g c d ( a , b ) ax+by=gcd(a,b) ax+by=gcd(a,b)的求解过程即可。
而对于 a x + b y = g c d ( a , b ) ax+by=gcd(a,b) ax+by=gcd(a,b)的求解,也只是在 a ′ x + b ′ y = 1 a'x+b'y=1 a′x+b′y=1的基础上进行的拓展。
所以本质就是用 e x g c d exgcd exgcd求解 a ′ x + b ′ y = 1 a'x+b'y=1 a′x+b′y=1
由裴蜀定理可知,若 g c d ( a , b ) = d gcd(a,b)=d gcd(a,b)=d,则一定存在一组整数解 ( x , y ) (x,y) (x,y)满足 a x + b y = d ax+by=d ax+by=d。
由欧几里得算法: g c d ( a , b ) = g c d ( b , a m o d b ) gcd(a,b)=gcd(b,a\:mod\:b) gcd(a,b)=gcd(b,amodb)
所以,原问题求 ( a x + b y = d ) (ax+by=d) (ax+by=d)的通解,等价于求: b x + ( a m o d b ) y = d bx+(a\:mod\:b)y=d bx+(amodb)y=d的通解。
而求 b x + ( a m o d b ) y = d bx+(a\:mod\:b)y=d bx+(amodb)y=d的通解,等价于求 ( a m o d b ) x + b m o d ( a m o d b ) y = d (a\:mod\:b)x + b\:mod\:(a\:mod\:b)y=d (amodb)x+bmod(amodb)y=d的通解…。
很眼熟对吧?这类似于欧几里得算法的求解过程,这个步骤是有限的,我们只要不断的递归下去,一定有尽头。
尽头就是: b = 0 b=0 b=0
当我们求到尽头的时候,原方程转化为: a ′ x = d a'x=d a′x=d 有整数解,解为: x = d a ′ x=\frac{d}{a'} x=a′d
一个约定俗成的事是可以直接令 x = 1 , y = 0 x=1,y=0 x=1,y=0
我们一般关注的不是特解,而是通解/最小正整数解,所以特解是什么都无所谓。
怎么做?
我们发现,即使我们不知道 a x + b y = c ax+by=c ax+by=c的解,我们仍然可以通过上述方法不断迭代,直到迭代到尽头。此时,我们就可以求出
考虑任意两层 ( x , y ) (x,y) (x,y)之间的转化:
设当前层为: ( x 1 , y 1 ) (x_1,y_1) (x1,y1),下一层为: ( x 2 , y 2 ) (x_2,y_2) (x2,y2)
联立:
{
a
x
1
+
b
y
1
=
d
b
x
2
+
[
a
m
o
d
b
]
y
2
=
d
\begin{cases} ax_1+by_1=d \\ bx_2+[a\:mod\:b]y_2=d \end{cases}
{ax1+by1=dbx2+[amodb]y2=d
中间状态:
{
a
x
1
+
b
y
1
=
d
1
b
x
2
+
(
a
−
[
a
b
]
∗
b
)
y
2
=
d
→
a
y
2
+
b
(
x
2
−
[
a
b
]
y
2
)
=
d
\begin{cases} ax_1+by_1=d1\\ bx_2+(a-[\frac{a}{b}]*b)y_2=d\rightarrow ay_2+b(x_2-[\frac{a}{b}]y_2)=d \end{cases}
{ax1+by1=d1bx2+(a−[ba]∗b)y2=d→ay2+b(x2−[ba]y2)=d
系数对齐:
{
a
x
1
+
b
y
1
=
d
a
y
2
+
b
(
x
2
−
[
a
b
]
y
2
)
=
d
\begin{cases} ax_1+by_1=d\\ ay_2+b(x_2-[\frac{a}{b}]y_2)=d \end{cases}
{ax1+by1=day2+b(x2−[ba]y2)=d
得到了相邻两层之间的整数解
(
x
,
y
)
(x,y)
(x,y)的转移方程:
{
x
1
=
y
2
y
1
=
x
2
−
[
a
b
]
y
2
\begin{cases} x_1=y_2\\ y_1=x_2-[\frac{a}{b}]y_2 \end{cases}
{x1=y2y1=x2−[ba]y2
先直接地给出代码:
int exgcd (int a, int b, int &x, int &y) {
if (!b) {x = 1; y = 0; return a;}
int d = exgcd (b, a % b, x, y);
int x2 = x, y2 = y;
x = y2, y = x2 - a / b * y2;
return d;
}
怎么写?
对于第i层, x i = y i + 1 x_i=y_{i+1} xi=yi+1,因为算法的特性,我们一开始并不知道 x , y x,y x,y的值,所以我们不能对其进行任
何运算,而是需要递归到底部对 x , y x,y x,y进行初始化,并在回溯过程中才能更新每一层的 x , y x,y x,y。(要完成这样的操作,我们需要传入 x , y x,y x,y的引用。)
而回溯的时候, x , y x,y x,y 的状态都是上一层的状态: ( x 2 , y 2 ) (x_2,y_2) (x2,y2)表示上一层的 ( x , y ) (x,y) (x,y)
int x2 = x, y2 = y;
x = y2, y = x2 - a / b * y2;
这并不难记,一旦忘记了相邻两层之间的
x
,
y
x,y
x,y的传递关系,利用欧几里得算法推导即可。
{
a
x
1
+
b
y
1
=
d
b
x
2
+
[
a
m
o
d
b
]
y
2
=
d
\begin{cases} ax_1+by_1=d \\ bx_2+[a\:mod\:b]y_2=d \end{cases}
{ax1+by1=dbx2+[amodb]y2=d
正常我们直接抄板子就行了:
int exgcd (int a, int b, int &x, int &y) {
if (!b) {x = 1; y = 0; return a;}
int d = exgcd (b, a % b, y, x);
y -= a / b * x;
return d;
}
值得注意的是,我们求出来的 ( x 1 , y 1 ) (x_1,y_1) (x1,y1)是关于方程: a x + b y = 1 ax+by=1 ax+by=1 的一个解。
如何去求通解?
其实通过数学直觉,我们不难想出,如果 x x x增加 Δ x \Delta x Δx , 相当于方程左边增加了 a Δ x a \Delta x aΔx ,我们要想方程右边不变的话,设让 y y y减去 Δ y \Delta y Δy
a ( x + Δ x ) + b ( y − Δ y ) = 1 a(x+\Delta x) + b(y-\Delta y)=1 a(x+Δx)+b(y−Δy)=1
可以得到等式: a Δ x = b Δ y a\Delta x = b\Delta y aΔx=bΔy
∴ Δ x Δ y = b a \therefore \frac{\Delta x}{\Delta y}=\frac{b}{a} ∴ΔyΔx=ab
接下来我们将右边的分式约分为最简式子即可: b a = b g c d ( a , b ) a g c d ( a , b ) \frac{b}{a}=\frac{\frac{b}{gcd(a,b)}}{\frac{a}{gcd(a,b)}} ab=gcd(a,b)agcd(a,b)b
即除以它们的最大公约数。
通解:
{
x
=
x
1
+
b
g
c
d
(
a
,
b
)
×
k
y
=
y
1
−
a
g
c
d
(
a
,
b
)
×
k
\begin{cases} x=x_1+\frac{b}{gcd(a,b)}\times k\\ y=y_1-\frac{a}{gcd(a,b)}\times k \end{cases}
{x=x1+gcd(a,b)b×ky=y1−gcd(a,b)a×k