扩展欧几里得算法——裴蜀(贝祖)等式

1.经典欧几里得算法


首先我们回顾一下经典欧几里得算法,可以用非常简短的递归代码实现.

public static int gcd(int m,int n){
    return n==0 ? m : gcd(n, m%n);
}

笔者之前在比赛中曾经见过相关算法题的应用,比如在网格内求三角形内部格点数和直线上的整数格点等等,此外还用到了皮克定理求面积: x = l 2 + n − 1 x = \dfrac{l}{2}+n-1 x=2l+n1其中 l 是三角形边界上的网格点和,n 是三角形内部网格点和。扯太远了,言归正传。

2.裴蜀等式

a x + b y = m ax+by=m ax+by=m 有整数解且   g c d ( a , b )   =   d \ gcd(a,b)\ =\ d  gcd(ab) = d ,则 m   m o d   n = 0 m\ mod \ n = 0 m mod n=0,反之也成立.

进一步推导:若 a x + b y = 1 ax+by=1 ax+by=1 存在有整数解且为无穷个,则 a , b a,b a,b 互质.

3.分析模型

( x 0 , y 0 ) (x0,y0) (x0,y0) 使 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 ) ∗ C ax+by=gcd(a,b)*C ax+by=gcd(a,b)C (C为常数)。

我们知道: a   m o d   b = k a\ mod\ b = k a mod b=k,则 a = b ∗ a b + k a=b*\dfrac{a}{b}+k a=bba+k ,由此观察到欧几里得算法的停止状态是 a = 1 , b = 0 a = 1,b = 0 a=1b=0,那么这能否给我们求解x和y提供一点思路呢?

首先设想,只要 a x + b y = g c d ( a , b ) ax+by=gcd(a,b) ax+by=gcd(a,b) 中 a 的系数是1,b 的系数无所谓我们不关心,这时我们有: a ∗ 1 + b ∗ 0 = g c d ( a , b ) a*1+b*0=gcd(a,b) a1+b0=gcd(a,b)

当然这是最终状态,因为此时的a和b并非原式的值,x和y的结果自然也不是。但是我们是否可以从最终态反推回初始态呢?

假设当前我们要处理的是求出a,b的最大公约数,并且求出x,y使得,而我们已经求出了下一个状态b和a%b的最大公约数,并且求出了一组 ( x 1 , y 1 ) (x1,y1) (x1y1)使得 b ∗ x 1 + ( a % b ) ∗ y 1 = g c d ( a , b ) b*x1+(a\%b)*y1=gcd(a,b) bx1+(a%b)y1=gcd(a,b)那么这两个相邻组是否存在一定的关系呢?我们转化一下原式为:

g c d ( a , b ) = a x + b y = b ∗ x 1 + ( a − a b ∗ b ) ∗ y 1 = a ∗ y 1 + b ∗ ( x 1 − a b ∗ y 1 ) gcd(a,b) =ax+by = b*x1+(a-\frac{a}{b}*b)*y1 = a*y1+b*(x1-\frac{a}{b}*y1) gcd(a,b)=ax+by=bx1+(abab)y1=ay1+b(x1bay1)

很显然,我们得到了一组递推公式:
x = y 1 x = y1 x=y1 y = x 1 − a b ∗ y 1 y = x1 -\frac{a}{b}*y1 y=x1bay1

那么如何递推呢,我们举个栗子: 2 x + 7 y = 1 2x +7y = 1 2x+7y=1首先我们根据欧几里得已知每一步的,分别是:

( 2 , 7 ) , ( 7 , 2 ) ( 2 , 1 ) , ( 1 , 0 ) (2,7),(7,2)(2,1),(1,0) (2,7),(7,2)(2,1),(1,0) 我们根据递推公式可以逆推得到每一步的x,y的值: ( 1 , 0 ) , ( 0 , 1 ) , ( 1 , − 3 ) , ( − 3 , 1 ) (1,0),(0,1),(1,-3),(-3,1) (1,0),(0,1),(1,3),(3,1)看到这里奇迹发生了,2*(-3)+7*1 = 1,答案就是
( x , y ) = ( − 3 , 1 )   ! ! ! (x,y)=(-3,1)\ !!! (x,y)=(3,1) !!!
我们同时可以得到通解: x = x 0 + b g c d ∗ t x = x0+\frac{b}{gcd}*t x=x0+gcdbt y = y 0 − a g c d ∗ t y = y0-\frac{a}{gcd}*t y=y0gcdat

4.Java代码

public static class ExdGcd {
	public static long x;
	public static long y;

	public static long gcd(long a, long b) {
		if (b == 0) {
			x = 1;
			y = 0;
			return a;
		}

		long res = gcd(b, a % b);
		long tmp = x;
		x = y;
		y = tmp - a / b * y;
		return res;
	}

	public static long linearEquation(long a, long b, long m) throws Exception {
		long gcd = gcd(a, b);
		if (m % gcd != 0)
			throw new Exception("NoAnswer");

		long d = m / gcd;
		x *= d;
		y *= d;
		return gcd;
	}
}

关于代码的一点说明。如果题目中等式右边是gcd的整数倍,那么求出来的x,y值也应该同时扩大相应的倍数,如果要求x或者y为最小的整数,则需要进行二次处理如:

long gcd = ExdGcd.linearEquation(a, b, m);
long k = ExdGcd.x;
long d = Math.abs(b / gcd);
k = (k % d + d) % d;

5.总结

扩展欧几里得属于算法中的数学问题,主要还是平时应该注重积累和练习套路,争取在竞赛、笔面试的时候能有敏锐的察觉能力,这只是一个原始的案例,可能会衍生出很多种考察方式,比如青蛙跳格子、同余方程组求解等等,时间有限不再叙写。

很久没有发博客了。主要是对于课程设计和算法还有参加过的竞赛归纳总结,这些工作对于自身的逻辑思维培养还是很有帮助的。只是现在才意识到发博的重要性,记得之前也有老师建议过我,多写下自己当时对问题的看法,这样能加深记忆力和对问题的理解程度,希望一起共勉吧!

你可能感兴趣的:(Java)