扩展欧几里得(exgcd)
扩展欧几里得的典型应用是解决形如
a × x + b × y = c a\times x+b\times y=c a×x+b×y=c
的二元一次方程的解的存在性问题以及求出特解与通解。
解的存在性问题
其实我们可以通过辗转相减法观察出来, a , b a,b a,b辗转相减的时候相当于 a × x + b × y a\times x+b\times y a×x+b×y中的 x , y x,y x,y不断变化的过程。我们通过前面的知识知道,这一个过程后,一定能得出最大公约数,所以 a × x + b × y = g c d ( a , b ) a\times x+b\times y=gcd(a,b) a×x+b×y=gcd(a,b)一定有解,那么是否 g c d ( x , y ) ≠ c gcd(x,y)≠c gcd(x,y)=c时就无解呢?
首先我们知道 c c c是 g c d ( a , b ) gcd(a,b) gcd(a,b)的倍数才可能有解,因为左边一定含有质因子 g c d ( a , b ) gcd(a,b) gcd(a,b),左边等于右边,则右边也一定含有 g c d ( a , b ) gcd(a,b) gcd(a,b),因此我们可以通过两边同时除以 g c d ( a , b ) gcd(a,b) gcd(a,b)得到一个新的式子:
k 1 × x + k 2 × y = c / g , g c d ( k 1 , k 2 ) = 1 k_1\times x+k_2\times y=c/g,gcd(k1,k2)=1 k1×x+k2×y=c/g,gcd(k1,k2)=1
由辗转相减可得: k 1 × x + k 2 × y = 1 k_1\times x+k_2\times y=1 k1×x+k2×y=1一定有解。所以, k 1 × x + k 2 × y = c / g k_1\times x+k_2\times y=c/g k1×x+k2×y=c/g一定有解。
所以,当 c c c是 g c d ( a , b ) gcd(a,b) gcd(a,b)的倍数时,方程一定有解。
特解与通解
所以我们只要求形如:
a × x + b × y = g c d ( a , b ) a\times x+b\times y=gcd(a,b) a×x+b×y=gcd(a,b)
然后,将解扩大 c / g c d ( a , b ) c/gcd(a,b) c/gcd(a,b)倍就可以得出方程的解了。
观察式子本质就是 a a a和 b b b辗转相减的过程。假设已经求出了
( a − b ) × x 1 + b × y 1 = g c d ( a , b ) (a-b)\times x_1+b\times y_1=gcd(a,b) (a−b)×x1+b×y1=gcd(a,b)
的解 x 1 , y 1 x_1,y_1 x1,y1,那么变换一下就可以得到
a × x 1 + b × ( y 1 − x 1 ) = g c d ( a , b ) a\times x_1+b\times (y_1-x_1)=gcd(a,b) a×x1+b×(y1−x1)=gcd(a,b)
我们可以得出
x = x 1 , y = y 1 − x 1 x=x_1,y=y_1-x_1 x=x1,y=y1−x1
得到了原方程的解,所以我们可以将原问题转化为一个规模更小的子问题。然后根据子问题的答案推出原问题的答案。考虑到这个辗转相减可以用辗转相除来替代,我们可以将求解原问题转化为求解
b × x 1 + a % b × y 1 = g c d ( a , b ) b\times x_1+a\% b\times y_1=gcd(a,b) b×x1+a%b×y1=gcd(a,b)
由:
a % b = a − a / b × b a\% b=a-a/b\times b a%b=a−a/b×b
代入得:
b × x 1 + ( a − a / b × b ) × y 1 = g c d ( a , b ) b\times x_1+(a-a/b\times b)\times y_1=gcd(a,b) b×x1+(a−a/b×b)×y1=gcd(a,b)
等价于:
a × y 1 + b × ( x 1 − a / b ∗ y 1 ) = g c d ( a , b ) a\times y_1+b\times (x_1-a/b*y_1)=gcd(a,b) a×y1+b×(x1−a/b∗y1)=gcd(a,b)
得到:
x = y 1 , y = x 1 − a / b ∗ y 1 x=y_1,y=x_1-a/b*y_1 x=y1,y=x1−a/b∗y1
因此,我们只需要一直缩小问题的规模,直到变成 g c d ( a , b ) × x + 0 × y = g c d ( a , b ) gcd(a,b)\times x+0\times y=gcd(a,b) gcd(a,b)×x+0×y=gcd(a,b),然后得到一组解 ( 1 , 0 ) (1,0) (1,0),在这组解反推回去,得到一组原方程的特解。该过程可以用一个函数来实现。
代码:
e x g c d exgcd exgcd
int exgcd(int a,int b,int &x,int &y){
int d=a;
if(b!=0){
d=exgcd(b,a%b,x,y);
x-=(a/b)*y;
swap(x,y);
}else{
x=1,y=0;
}
return d;
}
函数执行完毕后,代码里的 ( x , y ) (x,y) (x,y)就是 a × x + b × y = g c d ( a , b ) a\times x+b\times y=gcd(a,b) a×x+b×y=gcd(a,b)的一组特解。通解的变化规律就是 x , y x,y x,y的值往相反的方向变化,比如 x x x变大一点, y y y变小一点,使得答案不变,设这个变化的最小单位为 d 1 , d 2 d_1,d_2 d1,d2。
a × ( x + d 1 ) + b × ( y − d 2 ) = g c d ( a , b ) a\times (x+d_1)+b\times (y-d_2)=gcd(a,b) a×(x+d1)+b×(y−d2)=gcd(a,b)
a × d 1 = b × d 2 a\times d_1=b\times d_2 a×d1=b×d2
两边同除 g c d ( a , b ) gcd(a,b) gcd(a,b)令 k 1 = a / g c d ( a , b ) , k 2 = b / g c d ( a , b ) k_1=a/gcd(a,b),k_2=b/gcd(a,b) k1=a/gcd(a,b),k2=b/gcd(a,b)
k 1 × d 1 = k 2 × d 2 k_1\times d_1=k_2\times d_2 k1×d1=k2×d2
此时, g c d ( k 1 , k 2 ) = 1 gcd(k_1,k_2)=1 gcd(k1,k2)=1,显然 d 1 = k 2 , d 2 = k 1 d_1=k_2,d_2=k_1 d1=k2,d2=k1,可得通解为:
( x + k × d 1 , y − k × a / g c d ( a , b ) ) (x+k\times d_1,y-k\times a/gcd(a,b)) (x+k×d1,y−k×a/gcd(a,b))
特解的绝对值大小
我们通过递归函数的实现可以观察到,解的绝对值是跟 a , b a,b a,b的绝对值同一个级别的。(即传入与传出的数据不会相差太大,如传入的很小,传出的爆炸)。