扩展欧几里得算法推导

扩展欧几里得算法推导

//南昌理工ACM集训队

1 从简单的两个数a和b开始

欧几里得算法是用来求a与b的最大公约数的算法,也称辗转相除法,即以除数与余数反复做除法运算,当余数为0时,除数即为a与b的最大公约数

已知a和b,假设g为a与b的最大公约数,所以a与b都可以被g整除,
即a=g * x ,b=g * y。
我们又知道a%b可以求a与b的余数
不会吧不会吧不会还有人不知道
它的实质为

a-(a/b)*b

(a/b)向下取整
所以a 除 b的除数与余数为 b 与 a-(a/b)* b
把 a=g * x,b=g * y ,代入上面的式子,可以得到

y*g 与 [x-(x/y)*y]*g

我们发现经过运算过后两个式子依然是g的倍数,它们的最大公约数依然是g,那么像这样辗转相除到最后,即a为g,b为0的时候,两个式子的最大公约数自然还是g

这就是欧几里得在几千年前推算出的辗转相除法,翻译成c语言就是这个样子

我绝对不会告诉你这是gcd的模板

int gcd(int a, int b) { return b ? gcd(b, a % b) : a; }

2 扩展欧几里得算法

还是a和b
我们假设g还是a与b的最大公约数,即gcd(a,b)

因为a是g的倍数,b是g的倍数,那么一定存在一对x和y,使得 ax+by=g
因为a与b的除数与a / b的余数的最大公约数也为g,所以将b与a-(a/b)*b代入上式得出另一个式子

b* x+(a-(a/b)*b)*y=g

合并同类项之后可得

a* y+b* [x-(a/b)*y]=g

两式对比一下,我们不难发现

x2=y
y2=[x-(a/b)*y]

这就形成了一个递推的关系,那么我们就可以照着这个关系,通过递归的方式从小往大一步一步推出题目所需要的解

看到这里那么你已经掌握了大部分内容了,怎么样是不是很简单呢

3 老规矩,先上道模板题巩固一下ヾノ≧∀≦)o

1.P1082 [NOIP2012 提高组] 同余方程

题目很简单,就一句话,求关于x的同余方程 ax ≡ 1(mod b) 的最小正整数解,意思就是a*x % b=1,的最小整数解

那么我们利用模的实质,可以将式子转化为
ax-(ax/b)*b=1
因此肯定存在一对x和y,使得上述式子成立,即
ax+by=1
是不是很眼熟?跟上面的ax+by=g一模一样,而g是a与b的最大公约数,由裴蜀定理可知 gcd(a,b)=1, 那么我们就可以对着上面的思路把代码码出来辣。

先利用递归推出b=0,a=gcd(a,b),的辗转相除法最后的状态,随后代入x=1,y=0,再向上递归求出题目需要的x。
(y为辅助答案的值,因为b的值一开始为0,所以y的大小不影响结果)

void exgcd(int a,int b) {
	if (b == 0) {
		x = 1; y = 0;
		return;
	}
	exgcd(b, a % b);
	long long c = x; x = y;//c用来临时储存x
	y = c - (a / b) * y;
}

不过最后递归出来的答案有可能是最大负整数也有可能是最小正整数,所以我们要将输出的结果小小的处理一下

x = (x % b + b) % b;

最后的代码

#include<iostream>
using namespace std;
#define ll long long
ll x, y, T;
void exgcd(int a,int b) {
	if (b == 0) {
		x = 1; y = 0;
		return;
	}
	exgcd(b, a % b);
	ll c = x; x = y;
	y = c - (a / b) * y;
}
int main()
{
	ll a, b;
	cin >> a >> b;
	exgcd(a, b);
	x = (x % b + b) % b;
	cout<< x << endl;
}
2.老规矩,再来一题

P1516 青蛙的约会

不想算了这两只青蛙永远也走不到一起了

咳咳,我们的任务是帮助这两只青蛙看它们什么时候可以碰面。

输入x,y表示青蛙一开始的位置,m , n表示青蛙跳一次的距离,L表示这个数轴的总长度。

那么我们假设两只青蛙可以碰面,并且需要跳i次才能碰面,所以青蛙A就要跳m* i+x的距离,青蛙B就要跳n* i+y的距离,它们两个可以碰面,所以

(m* i+x)%L = (n* i+y) % L

通过扩展欧几里得算法可以转化成

m* i + x + L * j1 = n* i + y + L * j2

两边合并一下变成

( m - n )* i + L * (j1 - j2) = y - x

所以存在j = j1 - j2,使得上式成立,我们设h=m - n,f = y - x,j = j1 - j2,得到下面

h * i+ L * j = f

离扩展欧几里得的ax+by=g就差一步了,因此我们设h * i+L * j=gcd( h, L),这样就可以用欧几里得算法求出 i 了,最后输出的时候将 i 乘 f / gcd( h , L),就可以得出最后的结果

思路理完之后开码

当然在这题会出现m < n 的情况,我们就要将 m 和 n处理一下,将它改成正数

if (h < 0) {
		h = n - m;
		f = x - y;
	}

这题最后我们还需要判断我们输出的答案是否能让青蛙A和青蛙B碰面,所以我们拿出我们上面推出来的式子将 i 代进去看是否满足要求

if ((m*i+x)% L != (n*i+y)% L) {
		cout << "Impossible" << endl;
		return 0;
	}

完整代码

#include<iostream>
using namespace std;
#define ll long long
ll x, y, m, n, L, i, j, h, f, g;
void exgcd(ll a,ll b) {
	if (b == 0) {
		i = 1;
		j = 2;
		g = a;
		return;
	}
	exgcd(b, a % b);
	ll c = i; i = j;
	j = c - (a / b) * j;
}
int main()
{
	cin >> x >> y >> m >> n >> L;
	h = m - n;
	f = y - x;
	if (h < 0) {
		h = n - m;
		f = x - y;
	}
	exgcd(h, L);
	i = (f / g * i % (L / g) + L / g) % (L / g);//处理负数答案
	if ((m*i+x)%L!=(n*i+y)%L) {
		cout << "Impossible" << endl;
		return 0;
	}
	cout << i << endl; 
}

4.小结

当我们看到类似取模或者同余的题目时,就要思考一下能否利用扩展欧几里得定理去将题目解出来。如果有可能,就要想办法列出形如 ax+by=gcd( a , b ) 的等式,利用扩欧的模板,将题目所需要的x或者y求出来。

谢谢你看到最后ლ(╹◡╹ლ)

本人小白,如有不对欢迎指正

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值