欧几里得算法
欧几里得算法
要求两个数的最大公约数,我们可以利用欧几里得算法实现。
//要求a >= b >= 0
gcd(a, b){
if(b == 0){
return a;
}
return gcd(b, a % b)
}
先来讨论正确性,设g是a和b的最大公约数,那么a(modb)=a−kb=g(ag−kbg),也是g的倍数。同理设g′是b和a(modb)的最大公约数,那么有a=a(modb)+kb=g′(a(modb)g′+kbg′),因此g′也是a的因子。结合这两条性质可以直接推出g=g′。因此我们可以借助计算b和a(modb)的最大公约数g′来得到a和b的最大公约数g。
之后来讨论时间复杂度,若a≥2b,那么a(modb)<a2。否则a<2b,则a(modb)≤a−b<a2。因此不管哪种情况,较大的数在下次递归时都至少会减少一半。因此可以直接得出gcd
递归调用的总次数为O(logab),由于每次递归都是常数时间复杂度,因此这也就是我们要的时间复杂度。
由于a和b的最小公倍数等于abg,其中g=gcd(a,b)。因此我们可以直接推出最小公倍数算法:
//要求a >= b > 0
lcm(a, b){
return a / gcd(a, b) * b; // 先除法后乘法的目的是防止乘法溢出
}
下面来了解以下欧几里得算法的性质。
欧几里得算法还有一个非常重要的特性,就是我们要计算n个数的最大公约数,实际时间复杂度应该是O(n+log2M)而不是O(nlog2M),其中M是这n个数中的最大值。
扩展欧几里得算法
扩展欧几里得算法可以帮助我们解决下面一类问题:
ax+by=c其中a和b已知,且c=gcd(a,b)。要求我们找到一对合法的(x,y)。
当b=0的时候,我们一定有a⋅1+b⋅0=c,接下来考虑如果我们已知bx+(a−kb)y=c,其中k=⌊ab⌋。可以得出
ay+b(x−ky)=c利用这个公式可以直接推出扩展欧几里得算法。
extgcd(a, b){
if(b == 0){
return {1, 0};
}
{x, y} = extgcd(b, a % b);
return {y, x - a / b * y};
}
了解了扩展欧几里得原理后,我们可以利用它在模运算下计算x−1。对于模运算下求逆,如果模的数是素数,我们可以用费马小定理计算,其时间复杂度是严格Θ(log2p),其中p是模数。但是如果我们用扩展欧几里得算法来求逆,那得到的是O(log2p),即上界为此,但是实际上扩展欧几里得算法会快得多(实践中辗转次数非常少),并且扩欧不要求p是素数,只要求p和x互质。
扩欧还有一个非常重要的性质,就是我们可以发现算法中出现了乘法和减法运算,那么是否有可能在计算过程中出现数值溢出而导致结果错误呢?事实上这是不可能的,我们可以证明除非b=0的情况,有(x,y)=(1,0),其余情况下都满足|x|<b且|y|<a。这里给一下证明:
考虑上面算法回退的时候,第一次满足b>c,则此时{x, y} = extgcd(b, a % b);
得到的x=0,y=1,此时一定满足条件。现在假设该行返回的结果(x,y)满足条件|x|<a−kb,|y|<b,那么我们接下来证明return {y, x - a / b * y};
这一行返回的结果也满足条件。很显然y<b是已经成立的了,因此我们只需要证明|x−ky|<a即可。
由于bx+(a−kb)y=c<b,因此一定有x>0且y<0或x<0且y>0中的一方成立。
- 如果x>0且y<0成立,则由于y<0此时x−ky>0一定成立,根据x−ky<(a−kb)−ky<a−kb−kb=a,因此有|x−ky|<a成立。
- 如果x<0且y>0成立,则由于y>0此时x−ky<0一定成立,根据x−ky>−(a−kb)−ky>−a+kb−kb=−a,因此有|x−ky|<a成立。
可以发现任意情况下都有|x−ky|<a。根据归纳法可以证明结论。
题目1:给定整数a1,…,an,判断是否存在一组整数变量x1,…,xn,满足∑ni=1aixi=c。
这实际上是扩欧的一个拓展,对于n=2的情况实际上就是扩欧。不难推出结果成立当且仅当gcd(a1,…,an)能整除c。
题目2:给定整数a1,…,an,判断是否存在一组非负整数变量x1,…,xn,满足∑ni=1aixi=c。
首先如果所有的整数都是同号,那么c必须同号。这个问题变成经典DP,时间复杂度为O(nc)。
如果ai中存在负数−q和正数p,那么我们可以通过−qp+(q−1)p=−p来得到−p,同理可以得到q。同理这样我们可以认为xi可以为负数,这与题目1相同。