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+n−1其中 l 是三角形边界上的网格点和,n 是三角形内部网格点和。扯太远了,言归正传。
若 a x + b y = m ax+by=m ax+by=m 有整数解且 g c d ( a , b ) = d \ gcd(a,b)\ =\ d gcd(a,b) = 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 互质.
求 ( 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=b∗ba+k ,由此观察到欧几里得算法的停止状态是 a = 1 , b = 0 a = 1,b = 0 a=1,b=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) a∗1+b∗0=gcd(a,b)
当然这是最终状态,因为此时的a和b并非原式的值,x和y的结果自然也不是。但是我们是否可以从最终态反推回初始态呢?
假设当前我们要处理的是求出a,b的最大公约数,并且求出x,y使得,而我们已经求出了下一个状态b和a%b的最大公约数,并且求出了一组 ( x 1 , y 1 ) (x1,y1) (x1,y1)使得 b ∗ x 1 + ( a % b ) ∗ y 1 = g c d ( a , b ) b*x1+(a\%b)*y1=gcd(a,b) b∗x1+(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=b∗x1+(a−ba∗b)∗y1=a∗y1+b∗(x1−ba∗y1)
很显然,我们得到了一组递推公式:
x = y 1 x = y1 x=y1 y = x 1 − a b ∗ y 1 y = x1 -\frac{a}{b}*y1 y=x1−ba∗y1
那么如何递推呢,我们举个栗子: 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+gcdb∗t y = y 0 − a g c d ∗ t y = y0-\frac{a}{gcd}*t y=y0−gcda∗t
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;
扩展欧几里得属于算法中的数学问题,主要还是平时应该注重积累和练习套路,争取在竞赛、笔面试的时候能有敏锐的察觉能力,这只是一个原始的案例,可能会衍生出很多种考察方式,比如青蛙跳格子、同余方程组求解等等,时间有限不再叙写。
很久没有发博客了。主要是对于课程设计和算法还有参加过的竞赛归纳总结,这些工作对于自身的逻辑思维培养还是很有帮助的。只是现在才意识到发博的重要性,记得之前也有老师建议过我,多写下自己当时对问题的看法,这样能加深记忆力和对问题的理解程度,希望一起共勉吧!