上一次发博客已然是将近四年前,疫情还没开始的时候。
在这段时间里我暂别了OI,但是兜兜转转还是回到了计算机这一我一直向往的领域。
去年这个时候也动笔尝试写过一篇博客,但是工程量似乎比我想象中的要大些,自然烂尾了。
但今天登录的时候意外发现这几年里我的文章零零星星地有人点赞收藏,甚至有人关注我,实在不胜感激。
请注意,这篇博客里的代码实现有些问题,请不要参考代码
言归正传,扩展欧几里得算法是在广义欧几里得除法,也就是辗转相除法的过程中得出裴祖定理
s
⋅
a
+
t
⋅
b
=
(
a
,
b
)
s·a+t·b=(a,b)
s⋅a+t⋅b=(a,b)中
s
s
s与
t
t
t的方法。
文中我会使用%表示求余
为什么辗转相除法可以得到s和t?
辗转相除法就是反复利用
a
=
q
b
+
c
,
q
∈
Z
a=qb+c,q\in\mathbb{Z}
a=qb+c,q∈Z时,
gcd
(
a
,
b
)
=
gcd
(
b
,
c
)
\gcd(a,b)=\gcd(b,c)
gcd(a,b)=gcd(b,c)这一定理计算
gcd
(
a
,
b
)
\gcd(a,b)
gcd(a,b)的过程。在实际应用中表现为
a
=
q
b
+
r
a=qb+r
a=qb+r其中
q
=
⌊
a
b
⌋
,
r
=
a
%
b
q=\lfloor\frac{a}{b}\rfloor,r=a\%b
q=⌊ba⌋,r=a%b再令
b
,
r
b,r
b,r代替
a
,
b
a,b
a,b进行运算,即
r
i
=
q
i
⋅
r
i
+
1
+
r
i
+
2
r_{i}=q_i·r_{i+1}+r_{i+2}
ri=qi⋅ri+1+ri+2
我们发现
r
i
+
2
=
r
i
−
q
i
+
2
⋅
r
i
+
1
r_{i+2}=r_i-q_{i+2}·r_{i+1}
ri+2=ri−qi+2⋅ri+1,经过多次代换,我们可以将最终的
r
n
r_n
rn用
q
i
q_i
qi与
a
,
b
a,b
a,b的线性组合表示
如何得到s和t
我们探究这个过程的第一步
a
=
q
1
b
+
a
%
b
a=q_1b+a\%b
a=q1b+a%b
a
%
b
=
a
−
q
1
b
a\%b=a-q_1b
a%b=a−q1b
在
s
1
a
+
t
1
b
=
a
%
b
s_1a+t_1b=a\%b
s1a+t1b=a%b中
s
1
=
1
,
t
1
=
−
q
1
s_1=1, t_1=-q_1
s1=1,t1=−q1
第二步则是
(
b
%
(
a
%
b
)
)
=
b
−
q
2
(
a
%
b
)
(b\%(a\%b))=b-q_2(a\%b)
(b%(a%b))=b−q2(a%b)
代换得
s
2
=
−
q
2
s
1
,
t
2
=
−
q
2
t
1
+
1
s_2=-q_2s_1,t_2=-q_2t_1+1
s2=−q2s1,t2=−q2t1+1
所以我们得到了递推公式:
s
1
=
1
,
t
1
=
−
q
1
s_1=1,t_1=-q_1
s1=1,t1=−q1
s
i
=
−
q
i
s
i
−
1
,
t
i
=
−
q
i
t
i
−
1
+
1
s_i=-q_is_{i-1},t_i=-q_it_{i-1}+1
si=−qisi−1,ti=−qiti−1+1
简略地编写程序如下:
#include<cstdio>
struct tri{
int g, s, t;
};
tri gcd(int a, int b, int s, int t) {
if(a % b == 0) return (tri){b, s, t};
return gcd(b, a % b, -(a / b) * s, -(a / b) * t + 1);
}
int main() {
int a, b; scanf("%d%d", &a, &b);
if(a < b) b ^=a, a ^= b, b ^= a;//swap a, b
tri ans = gcd(b, a % b, 1, -(a / b));
printf("gcd of %d and %d is %d, while %d * %d + %d * %d = %d\n",
a, b, ans.g,
ans.s, a, ans.t, b, ans.s * a + ans.t * b
);
main();
}
这个程序的主体部分在
a
<
b
a<b
a<b时会造成错误的结果,推测可能与我
a
,
b
,
s
,
t
a,b,s,t
a,b,s,t分别对应
b
,
a
%
b
,
s
,
t
b,a\%b,s,t
b,a%b,s,t的偷懒传参方式有关。
这个方法中
s
,
t
s,t
s,t的计算是与
a
,
b
a,b
a,b的变化同向的,故而适合笔算。