【算法】扩展欧几里得

扩展欧几里得是用来求出类似于

a x + b y = gcd ⁡ ( a , b ) ax+by=\gcd(a,b) ax+by=gcd(a,b)

的方程,满足 x x x y y y 为正整数。本博客会讲解此方程解法以及此算法的运用方法。


方程解法

根据辗转相除法(欧几里得法)可以得出

gcd ⁡ ( a , b ) = gcd ⁡ ( b , a   m o d   b ) \gcd(a,b)=\gcd(b,a\bmod b) gcd(a,b)=gcd(b,amodb)

x 0 x_{0} x0 y 0 y_{0} y0 是此方程中的一组特解,那么

a x + b y = b x 0 + ( a   m o d   b ) y 0 ax+by=bx_{0}+(a \bmod b)y_{0} ax+by=bx0+(amodb)y0

= b x 0 + ( a − b ⌊ a b ⌋ ) y 0 =bx_{0}+(a-b\left \lfloor \frac{a}{b}\right \rfloor)y_{0} =bx0+(abba)y0

= b x 0 + a y 0 − b ⌊ a b ⌋ y 0 =bx_{0}+ay_{0}-b\left \lfloor \frac{a}{b}\right \rfloor y_{0} =bx0+ay0bbay0

= a y 0 + b ( x 0 − ⌊ a b ⌋ y 0 ) = gcd ⁡ ( a , b ) =ay_{0}+b(x_{0}-\left \lfloor \frac{a}{b}\right \rfloor y_{0})=\gcd(a,b) =ay0+b(x0bay0)=gcd(a,b)

可得出答案

x = y 0 , y = x 0 − ⌊ a b ⌋ y 0 x=y_{0}, y=x_{0}-\left \lfloor \frac{a}{b}\right \rfloor y_{0} x=y0,y=x0bay0

其中, x x x y y y 初始时无法得出,所以可任意赋初值。

因为任何正整数与 0 0 0 的最大公因数都为自己,所以当 b = 0 b=0 b=0 ,且需满足原方程, x = 1 , y = 0 x=1,y=0 x=1,y=0

代码

#include<bits/stdc++.h>
using namespace std;
int a,b,x,y;
void exgcd(int a,int b,int &x,int &y)
{
	if(!b)
	{
		x=1;y=0;
		return ;
	}
	exgcd(b,a%b,x,y);
	int t=x;
	x=y;
	y=t-(a/b)*y;
}
int main()
{
	scanf("%d %d",&a,&b);
	exgcd(a,b,x,y);
	printf("%d %d",&x,&y);
	return 0;
}

二元一次不定方程

扩展欧几里得的一个主要作用就是来求二元一次不定方程。

二元一次方程是一个类似于以下形式的一个方程

a x + b y = c ax+by=c ax+by=c

我们发现,这个方程的形式类似于扩展欧几里得的形式,又根据裴蜀定理,则可以得出

gcd ⁡ ( a , b ) ∣ c \gcd(a,b)\mid c gcd(a,b)c

那么如果不满足当前性质,则不能有解

方程特解

根据上述性质,我们可以得出 c gcd ⁡ ( a , b ) \frac{c}{\gcd(a,b)} gcd(a,b)c 一定为整数,则如果将扩展欧几里得原式两边同时乘上一个,一定满足扩展欧几里得性质( x x x y y y 为非负整数)

a c x 0 gcd ⁡ ( a , b ) + b c y 0 gcd ⁡ ( a , b ) = c a \frac{cx_{0}}{\gcd(a,b)}+b \frac{cy_{0}}{\gcd(a,b)}=c agcd(a,b)cx0+bgcd(a,b)cy0=c

那么特解即为:

x 1 = c x 0 gcd ⁡ ( a , b ) , y 1 = c y 0 gcd ⁡ ( a , b ) x_{1}=\frac{cx_{0}}{\gcd(a,b)},y_{1}=\frac{cy_{0}}{\gcd(a,b)} x1=gcd(a,b)cx0,y1=gcd(a,b)cy0

方程通解

我们再考虑构造出一个等式

a x 1 + b y 1 + d a b − d a b = c ax_{1}+by_{1}+dab-dab=c ax1+by1+dabdab=c

a ( x 1 + d b ) + b ( y 1 − d a ) = c a(x_{1}+db)+b(y_{1}-da)=c a(x1+db)+b(y1da)=c

因为我们需要满足扩展欧几里得的性质,所以需要保证 d a da da d b db db 均为非负整数。通解即为:

x = x 1 + k d b , y = y 1 − k d a x=x_{1}+kdb,y=y_{1}-kda x=x1+kdb,y=y1kda

其中, k k k 为任意正整数

方程最值问题

首先,因为通解的和是一定的,所以 y y y 一定随着 x x x 的增大而减小。 k k k 越大, x x x 越大, y y y 越小,反之亦然。

所以,我们可以先求出 x x x y y y 的最小值再带入求出它们的最大值。

如果我们要使 x x x 最小,那么我们就要将 x x x 减去 k k k d b db db。但是我们并不知道 k k k,所以便要一直向下减一直到不能再减了(再减就不为正整数了),那我们就考虑有没有一种方法可以直接求出而不用一直减。

考虑与 d b db db 取模,取模后的答案,也就为 x x x 的最小非负整数解。

那我们就要使 d b db db 尽可能的小,也就是使 d d d 尽可能的小( b b b 已经给你了),且还满足 d b db db 为非负整数。显然, d = 1 g c d ( a , b ) d=\frac{1}{gcd(a,b)} d=gcd(a,b)1

那么 x x x 的最小非负整数解即为

x 2 = ( c x 0 gcd ⁡ ( a , b )   m o d   b g c d ( a , b ) + b g c d ( a , b ) )   m o d   b g c d ( a , b ) x_{2}=(\frac{cx_{0}}{\gcd(a,b)}\bmod \frac{b}{gcd(a,b)}+\frac{b}{gcd(a,b)})\bmod \frac{b}{gcd(a,b)} x2=(gcd(a,b)cx0modgcd(a,b)b+gcd(a,b)b)modgcd(a,b)b

PS:注意这里一定要加一下再模一下,不然会为负数(我也不知道为什么会为负数)

将其设为 x 2 x_{2} x2 随后带入求 y y y 的最大非负整数解 y 3 y_{3} y3,即:

y 3 = c − a x 2 b y_{3}=\frac{c-ax_{2}}{b} y3=bcax2

反之亦然

代码

#include<bits/stdc++.h>
using namespace std;
int a,b,x,y,c;
int exgcd(int a,int b,int &x,int &y)
{
	if(!b)
	{
		x=1;y=0;
		return a;
	}
	int gcd=exgcd(b,a%b,x,y);
	int t=x;
	x=y;
	y=t-(a/b)*y;
	return gcd;
}
int main()
{
	scanf("%d %d %d",&a,&b,&c);
	int gcd=exgcd(a,b,x,y);
	if(c%gcd)
		puts("impossible!");
	else
	{
		int minx=(((c*x)/gcd)%(b/gcd)+(b/gcd))%(b/gcd);
		int miny=(((c*y)/gcd)%(a/gcd)+(a/gcd))%(a/gcd);
		int maxx=(c-b*miny)/a;
		int maxy=(c-a*minx)/b;
		printf("%d %d %d %d",minx,miny,maxx,maxy);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值