裴蜀定理
裴蜀定理:对任意的整数a、b,关于未知数x、y的线性丢番图方程ax+by=m,当且仅当m是gcd(a,b)的整数倍时方程有解。
该方程称为裴蜀等式。
证明
a
x
+
b
y
=
g
c
d
(
a
,
b
)
ax+by=gcd(a,b)
ax+by=gcd(a,b)时有解:
设
d
=
g
c
d
(
a
,
b
)
d=gcd(a,b)
d=gcd(a,b),则有
d
∣
a
d\mid a
d∣a,
d
∣
b
d\mid b
d∣b,因此
∀
x
,
y
∈
Z
,
d
∣
a
x
+
b
y
\forall x,y\in\mathbb{Z},d\mid ax+by
∀x,y∈Z,d∣ax+by,即
d
∣
m
d\mid m
d∣m
设
s
s
s是最小的
m
m
m,则有
d
∣
s
d\mid s
d∣s
令
q
=
⌊
a
s
⌋
,
r
=
a
%
s
=
a
−
q
s
=
a
−
q
(
a
x
+
b
y
)
=
a
(
1
−
q
x
)
+
b
(
−
q
y
)
q=\lfloor\frac{a}{s}\rfloor,r=a\%s=a-qs=a-q(ax+by)=a(1-qx)+b(-qy)
q=⌊sa⌋,r=a%s=a−qs=a−q(ax+by)=a(1−qx)+b(−qy),可见
r
r
r也是
x
,
y
x,y
x,y的线性组合,因为
s
s
s是
x
,
y
x,y
x,y线性组合的最小值,
0
≤
r
<
s
0\le r<s
0≤r<s,所以
r
=
0
r=0
r=0,则有
s
∣
a
s\mid a
s∣a
同理有
s
∣
b
s\mid b
s∣b,因此
s
s
s是
a
,
b
a,b
a,b的公约数,而
d
d
d是
a
,
b
a,b
a,b的最大公约数,因此有
s
∣
d
s\mid d
s∣d
综上,
s
=
d
s=d
s=d
证明
a
x
+
b
y
=
m
ax+by=m
ax+by=m有解的充要条件是
g
c
d
(
a
,
b
)
∣
m
gcd(a,b)\mid m
gcd(a,b)∣m:
充分性:若
g
c
d
(
a
,
b
)
∣
m
gcd(a,b)\mid m
gcd(a,b)∣m,则
m
=
k
∗
g
c
d
(
a
,
b
)
m=k*gcd(a,b)
m=k∗gcd(a,b),设
a
x
+
b
y
=
g
c
d
(
a
,
b
)
ax+by=gcd(a,b)
ax+by=gcd(a,b)的一组解为
x
0
,
y
0
x_0,y_0
x0,y0,显然
k
x
0
,
k
y
0
kx_0,ky_0
kx0,ky0是
a
x
+
b
y
=
m
ax+by=m
ax+by=m的一组解。
必要性:设
d
=
g
c
d
(
a
,
b
)
d=gcd(a,b)
d=gcd(a,b),则有
d
∣
a
d\mid a
d∣a,
d
∣
b
d\mid b
d∣b,因此
∀
x
,
y
∈
Z
,
d
∣
a
x
+
b
y
\forall x,y\in\mathbb{Z},d\mid ax+by
∀x,y∈Z,d∣ax+by,即
d
∣
m
d\mid m
d∣m
扩展欧几里得算法
显然,当m等于k倍的gcd(a,b)时,裴蜀等式的解是m恰好等于gcd(a,b)时解的k倍。
因此,对任意的裴蜀等式,我们只需考察m恰好等于gcd(a,b)时裴蜀等式的解。
扩展欧几里得算法就是用来求当m恰好等于gcd(a,b)时裴蜀等式的解的一种算法。
设
a
∗
x
1
+
b
∗
y
1
=
g
c
d
(
a
,
b
)
,
b
∗
x
2
+
(
a
%
b
)
∗
y
2
=
g
c
d
(
b
,
a
%
b
)
a*x_1+b*y_1=gcd(a,b),b*x_2+(a\%b)*y_2=gcd(b,a\%b)
a∗x1+b∗y1=gcd(a,b),b∗x2+(a%b)∗y2=gcd(b,a%b)
由欧几里得算法(辗转相除法)可知,
g
c
d
(
a
,
b
)
=
g
c
d
(
b
,
a
%
b
)
gcd(a,b)=gcd(b,a\%b)
gcd(a,b)=gcd(b,a%b)
∴
a
∗
x
1
+
b
∗
y
1
=
b
∗
x
2
+
(
a
%
b
)
∗
y
2
\therefore a*x_1+b*y_1=b*x_2+(a\%b)*y_2
∴a∗x1+b∗y1=b∗x2+(a%b)∗y2
又
∵
a
%
b
=
a
−
⌊
a
b
⌋
∗
b
\because a\%b=a-\lfloor\frac{a}{b}\rfloor*b
∵a%b=a−⌊ba⌋∗b
∴
a
∗
x
1
+
b
∗
y
1
=
b
∗
x
2
+
a
∗
y
2
−
⌊
a
b
⌋
∗
b
∗
y
2
\therefore a*x_1+b*y_1=b*x_2+a*y_2-\lfloor\frac{a}{b}\rfloor*b*y_2
∴a∗x1+b∗y1=b∗x2+a∗y2−⌊ba⌋∗b∗y2
移项得,
a
∗
(
x
1
−
y
2
)
+
b
∗
(
y
1
−
x
2
+
⌊
a
b
⌋
∗
y
2
)
=
0
a*(x_1-y_2)+b*(y_1-x_2+\lfloor\frac{a}{b}\rfloor*y_2)=0
a∗(x1−y2)+b∗(y1−x2+⌊ba⌋∗y2)=0
因此,
x
1
=
y
2
,
y
1
=
x
2
−
⌊
a
b
⌋
∗
y
2
x_1=y_2, y_1=x_2-\lfloor\frac{a}{b}\rfloor*y_2
x1=y2,y1=x2−⌊ba⌋∗y2就是一组解
根据上面的推导可知:对于一组线性方程ax+by=gcd(a,b),它的一组解
x
1
,
y
1
x_1,y_1
x1,y1可以通过另一组线性方程bx+(a%b)y=gcd(b, a%b)的一组解
x
2
,
y
2
x_2,y_2
x2,y2计算得出。这恰好满足递归的条件。
例如,当求(a,b)为(15,8)的解时,我们需要先计算(8,7)的解,进而需要先计算(7,1)的解,再进而需要先计算(1,0)的解。
显然,递归的边界是b=0
当b等于0时,方程变为ax=gcd(a,0),又因为gcd(a,0)=a,所以方程变为ax=a,解为x=1
这样,我们运用递归算法就可以求出任意一组线性方程ax+by=gcd(a,b)的一组解:
先不断递推,一定能推至b=0,b=0时解是已知的,回溯时用已经知道的
x
2
,
y
2
x_2,y_2
x2,y2,计算上一层的
x
1
,
y
1
x_1,y_1
x1,y1,这样就可以算出最开始的解。
代码如下:
void exgcd(int a, int b, int &x, int &y)
{
if (b == 0) x = 1;
else
{
exgcd(b, a % b, y, x);
y -= a / b * x;
}
}
该算法在递归中巧妙地运用了引用类型,使得代码无比简洁。
由于x,y参数是引用类型,因此在若干层递归的过程中,我们改变的一直是同一块内存中存储的数值。
exgcd(b, a % b, y, x);
这行代码是整个算法的核心。根据前文的推导,将a、b变为b、a%b很好理解,那么为什么要颠倒x,y的次序呢?
还是根据前文的推导
x
1
=
y
2
x_1=y_2
x1=y2,也就是说当前层的x和下一层的y相等,所以我们只需要将当前层的x引用传给下一层的y引用。引用可以理解为一个变量的别名,颠倒x,y的次序可以理解成:当前层的x和下一层的y是同一个变量的不同名称,他们其实是一个变量。 这样就做到了递归回溯后
x
1
=
y
2
x_1=y_2
x1=y2
那么
y
1
=
x
2
−
⌊
a
b
⌋
∗
y
2
y_1=x_2-\lfloor\frac{a}{b}\rfloor*y_2
y1=x2−⌊ba⌋∗y2是如何做到的呢?
由于x,y在递归的上下层中次序颠倒,使得回溯后
x
1
=
y
2
x_1=y_2
x1=y2,同样也有
y
1
=
x
2
y_1=x_2
y1=x2
即在执行这行代码后
x
1
=
y
2
x_1=y_2
x1=y2,
y
1
=
x
2
y_1=x_2
y1=x2
那么
y
1
=
x
2
−
⌊
a
b
⌋
∗
y
2
y_1=x_2-\lfloor\frac{a}{b}\rfloor*y_2
y1=x2−⌊ba⌋∗y2就变成了
y
1
=
y
1
−
⌊
a
b
⌋
∗
x
1
y_1=y_1-\lfloor\frac{a}{b}\rfloor*x_1
y1=y1−⌊ba⌋∗x1
于是就有了下面一行代码
y -= a / b * x;
要注意的是,裴蜀等式有解时一定有无穷多的解,这个算法求得的只是其中一组解。
代码应用如下:
#include<iostream>
using namespace std;
void exgcd(int a, int b, int& x, int& y)
{
if (b == 0) x = 1;
else
{
exgcd(b, a % b, y, x);
y -= a / b * x;
}
}
int main()
{
int a, b, x, y;
x = 0;
y = 0;
cin >> a >> b;
exgcd(a, b, x, y);
cout << x << " " << y << endl;
return 0;
}
最小正整数解
int min_x = (x % b + b) % b;//x的最小正整数解
int min_y = (y % a + a) % a;//y的最小正整数解
cout << min_x << " " << (x - min_x) / b * a + y << endl;//x的最小正整数解与其对应的y
cout << (y - min_y) / a * b + x << " " << min_y;//y的最小正整数解与其对应的x
证明:
不妨设
x
0
x_0
x0为
x
x
x的最小正整数解,
y
0
y_0
y0为
x
0
x_0
x0对应的
y
y
y
∵
a
∗
x
+
b
∗
y
=
g
c
d
(
a
,
b
)
\because a*x+b*y=gcd(a,b)
∵a∗x+b∗y=gcd(a,b)
∴
a
∗
x
+
a
∗
b
∗
k
+
b
∗
y
−
a
∗
b
∗
k
=
g
c
d
(
a
,
b
)
\therefore a*x+a*b*k+b*y-a*b*k=gcd(a,b)
∴a∗x+a∗b∗k+b∗y−a∗b∗k=gcd(a,b)
合并得,
a
∗
(
x
+
k
∗
b
)
+
b
∗
(
y
−
k
∗
a
)
=
g
c
d
(
a
,
b
)
a*(x+k*b)+b*(y-k*a)=gcd(a,b)
a∗(x+k∗b)+b∗(y−k∗a)=gcd(a,b)
因此,所有整数解都可写为
x
=
x
0
+
k
∗
b
,
y
=
y
0
−
k
∗
a
x=x_0+k*b,y=y_0-k*a
x=x0+k∗b,y=y0−k∗a的形式,即x多一个b,y就要少一个a
显然,若
x
x
x为正数,
x
%
b
=
(
x
0
+
k
∗
b
)
%
b
=
x
0
x\%b=(x_0+k*b)\%b=x_0
x%b=(x0+k∗b)%b=x0
若x为负数,
x
%
b
=
[
(
x
0
−
b
)
+
(
k
+
1
)
∗
b
]
%
b
=
x
0
−
b
x\%b=[(x_0-b)+(k+1)*b]\%b=x_0-b
x%b=[(x0−b)+(k+1)∗b]%b=x0−b
∴
x
%
b
+
b
=
x
0
或
x
0
+
b
\therefore x\%b+b=x_0或x_0+b
∴x%b+b=x0或x0+b
∴
(
x
%
b
+
b
)
%
b
=
x
0
\therefore(x\%b+b)\%b=x_0
∴(x%b+b)%b=x0
综上所述
x
x
x的最小正整数解为
(
x
%
b
+
b
)
%
b
(x\%b+b)\%b
(x%b+b)%b
同理可证
y
y
y的最小正整数解为
(
y
%
a
+
a
)
%
a
(y\%a+a)\%a
(y%a+a)%a
注意:一般情况下,x的最小正整数解与y的最小正整数解并不是一组解。