扩展欧几里得算法

裴蜀定理

裴蜀定理:对任意的整数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 da d ∣ b d\mid b db,因此 ∀ x , y ∈ Z , d ∣ a x + b y \forall x,y\in\mathbb{Z},d\mid ax+by x,yZ,dax+by,即 d ∣ m d\mid m dm
s s s是最小的 m m m,则有 d ∣ s d\mid s ds
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=aqs=aq(ax+by)=a(1qx)+b(qy),可见 r r r也是 x , y x,y x,y的线性组合,因为 s s s x , y x,y x,y线性组合的最小值, 0 ≤ r &lt; s 0\le r&lt;s 0r<s,所以 r = 0 r=0 r=0,则有 s ∣ a s\mid a sa
同理有 s ∣ b s\mid b sb,因此 s s s a , b a,b a,b的公约数,而 d d d a , b a,b a,b的最大公约数,因此有 s ∣ d s\mid d sd
综上, 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=kgcd(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 da d ∣ b d\mid b db,因此 ∀ x , y ∈ Z , d ∣ a x + b y \forall x,y\in\mathbb{Z},d\mid ax+by x,yZ,dax+by,即 d ∣ m d\mid m dm

扩展欧几里得算法

显然,当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) ax1+by1=gcd(a,b)bx2+(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 ax1+by1=bx2+(a%b)y2
∵ a % b = a − ⌊ a b ⌋ ∗ b \because a\%b=a-\lfloor\frac{a}{b}\rfloor*b a%b=abab
∴ 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 ax1+by1=bx2+ay2baby2
移项得, 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(x1y2)+b(y1x2+bay2)=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=x2bay2就是一组解

根据上面的推导可知:对于一组线性方程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=x2bay2是如何做到的呢?
由于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=x2bay2就变成了 y 1 = y 1 − ⌊ a b ⌋ ∗ x 1 y_1=y_1-\lfloor\frac{a}{b}\rfloor*x_1 y1=y1bax1
于是就有了下面一行代码

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) ax+by=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) ax+abk+byabk=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+kb)+b(yka)=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+kb,y=y0ka的形式,即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+kb)%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=[(x0b)+(k+1)b]%b=x0b
∴ x % b + b = x 0 或 x 0 + b \therefore x\%b+b=x_0或x_0+b x%b+b=x0x0+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的最小正整数解并不是一组解。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值