最大公约数(Greatest Common Divisor)
引题:
输入两个自然数x、y,求它们的最大公约数。
对于两个整数x和y,如果x/d个y/余数都为0,则d称为x和y的公约数,其中最大的称为x和y的最大公约数(Greatest Common Divisor)。举个例子,35和14的最大公约数gcd(35,14)为7.因为35的约数为{1,5,7,35},14的约数为{1,2,7,14},二者的公约数{1,7}中最大的是7。
输入:输入x、y,用1个空格隔开,占1行。
输出:输出最大公约数,占1行。
限制:
1
<
=
x
,
y
<
=
1
0
9
1<=x,y<=10^9
1<=x,y<=109
提示:对于整数x、y,如果x>=y,则x与y的最大公约数等于y与x%y的最大公约数。这里x%y表示x除以y之后的余数。
输入示例:147 105
输出实例:21
1. 初学者的思维:
将x和y中较小的一方用作n,让d从n自减至1,检查其是否能同时整除x和y,如果能返回当时的d。
伪代码:
gcd(x,y)
n = (x与y中较小的一个)
for d从n到1
if d是x和y的约数
return d
这个算法虽然能正确地输出结果,但最坏情况下要进行n次除法,无法在规定时间内处理完较大的数据。
2. 欧几里得算法(Euclidean algorithm,辗转相除法)
定理:当x>=y时,gcd(x,y)等于gcd(y,x除以y之后的余数)。
运用这条定理,可以快速求解x与y最大公约数的算法。
3. 定理证明
设d为a和b的公约数,则有:(l,m为常数)
a
=
l
∗
d
a=l * d
a=l∗d………………………………(1)
b
=
m
∗
d
b=m * d
b=m∗d………………………………(2)
设r=a%b,则有:(k为常数)
a
=
k
∗
b
+
r
a=k * b+r
a=k∗b+r……………………………(3)
将(1)(2)代入(3)得:
l
∗
d
=
k
∗
m
∗
d
+
r
l * d=k*m*d+r
l∗d=k∗m∗d+r
上式化简得:
r
=
(
l
−
k
∗
m
)
∗
d
r=(l-k*m)*d
r=(l−k∗m)∗d……………………(4)
所以由(4)易得:d为r的约数。
又由(2)易得:d为b的约数。
综上两点,易得:d为b和r的公约数。
即证明:a与b的公约数,与,b和r(r=a%b)的公约数相等。
因此,a和b的公约数集合等于b和r的公约数集合,二者的最大公约数自然也相等。
4. 举例and时间复杂度
74与54的最大公约数:
g
c
d
(
74
,
54
)
=
g
c
d
(
54
,
74
%
54
)
=
g
c
d
(
54
,
20
)
=
g
c
d
(
20
,
54
%
20
)
=
g
c
d
(
20
,
14
)
=
g
c
d
(
14
,
20
%
14
)
=
g
c
d
(
14
,
6
)
=
g
c
d
(
6
,
14
%
6
)
=
g
c
d
(
6
,
2
)
=
g
c
d
(
2
,
6
%
2
)
=
g
c
d
(
2
,
0
)
=
2
gcd(74,54)\\=gcd(54,74\%54)=gcd(54,20)\\=gcd(20,54\%20)=gcd(20,14)\\=gcd(14,20\%14)=gcd(14,6)\\=gcd(6,14\%6)=gcd(6,2)\\=gcd(2,6\%2)=gcd(2,0)\\=2
gcd(74,54)=gcd(54,74%54)=gcd(54,20)=gcd(20,54%20)=gcd(20,14)=gcd(14,20%14)=gcd(14,6)=gcd(6,14%6)=gcd(6,2)=gcd(2,6%2)=gcd(2,0)=2
时间复杂度大致为O(logb)。
5. 用递归函数求最大公约数
#include <iostream>
using namespace std;
int gcd(int x,int y)
{
return y ? gcd(y,x%y) : x;
}
int main()
{
int a,b;
cin >> a >> b;
cout << gcd(a,b) << endl;
}
6. 用循环求最大公约数
#include <iostream>
#include <algorithm>
using namespace std;
int gcd(int x,int y)
{
int r;
if(x<y) swap(x,y);//保证x>y
while(y>0)
{
r = x%y;
x = y;
y = r;
}
return x;
}
int main()
{
int a,b;
cin >> a >> b;
cout << gcd(a,b) << endl;
}
最小公倍数(Least Common Multiple,LCM)
1. 求最小公倍数的公式
求给定n个整数的最小公倍数(Least Common Multiple,LCM)。这里可以先用欧几里得算法求出最大公约数,然后用最大公约数求出两个数的最小公倍数。
公式:
已知:
gcd(a, b),是求a和b的最大公约数
lcm(a, b),是求a和b的最小公倍数
易得:
a
∗
b
=
g
c
d
(
a
,
b
)
∗
l
c
m
(
a
,
b
)
a*b = gcd(a,b) * lcm(a,b)
a∗b=gcd(a,b)∗lcm(a,b)
即
l
c
m
(
a
,
b
)
=
a
∗
b
/
g
c
d
(
a
,
b
)
lcm(a,b) = a * b / gcd(a,b)
lcm(a,b)=a∗b/gcd(a,b)
(注意,这样写法有可能会错,因为a * b可能因为太大 超出int 或者超出 long long)
所以推荐写成 :
l
c
m
(
a
,
b
)
=
a
/
g
c
d
(
a
,
b
)
∗
b
lcm(a,b) = a / gcd(a,b) * b
lcm(a,b)=a/gcd(a,b)∗b
2. 公式证明
- 求a和b的 g c d ( a , b ) gcd(a,b) gcd(a,b)
- 易得 a g c d ( a , b ) \frac{a}{gcd(a,b)} gcd(a,b)a 和 b g c d ( a , b ) \frac{b}{gcd(a,b)} gcd(a,b)b 这两个数互质,即 g c d ( a g c d ( a , b ) , b g c d ( a , b ) ) = 1 gcd(\frac{a}{gcd(a,b)},\frac{b}{gcd(a,b)}) =1 gcd(gcd(a,b)a,gcd(a,b)b)=1
- 所以,易知
l
c
m
(
a
,
b
)
=
g
c
d
(
a
,
b
)
∗
(
a
g
c
d
(
a
,
b
)
)
∗
(
b
g
c
d
(
a
,
b
)
)
lcm(a,b)=gcd(a,b)*(\frac{a}{gcd(a,b)})*(\frac{b}{gcd(a,b)})
lcm(a,b)=gcd(a,b)∗(gcd(a,b)a)∗(gcd(a,b)b),
化简: l c m ( a , b ) = a ∗ b g c d ( a , b ) lcm(a,b)=\frac{a*b}{gcd(a,b)} lcm(a,b)=gcd(a,b)a∗b,
即 a ∗ b = g c d ( a , b ) ∗ l c m ( a , b ) a*b=gcd(a,b)*lcm(a,b) a∗b=gcd(a,b)∗lcm(a,b)
3. 公式拓展
g c d ( k a , k b ) = k ∗ g c d ( a , b ) gcd(ka, kb) = k * gcd(a, b) gcd(ka,kb)=k∗gcd(a,b)
l c m ( k a , k b ) = k ∗ l c m ( a , b ) lcm(ka, kb) = k * lcm(a, b) lcm(ka,kb)=k∗lcm(a,b)
l c m ( S a , S b ) = S g c d ( a , b ) lcm(\frac{S}{a}, \frac{S}{b}) = \frac{S}{gcd(a, b)} lcm(aS,bS)=gcd(a,b)S
拓展欧几里得(Extended Euclid Algorithm)
任务:
对于给定的两个整数
a
、
b
a、b
a、b,求
a
x
+
b
y
=
g
c
d
(
a
,
b
)
ax+by=gcd(a,b)
ax+by=gcd(a,b)的解
(
x
,
y
)
(x,y)
(x,y)。
说明:
-
当b=0时,有x=1,y=0时成立。
-
当b>0时,在欧几里得算法的基础上,已知: g c d ( a , b ) = g c d ( b , a % b ) gcd(a,b)=gcd(b,a\%b) gcd(a,b)=gcd(b,a%b)
先递归求出x’,y’满足: g c d ( a , b ) = g c d ( b , a % b ) = b x ′ + ( a % b ) y ′ gcd(a,b)=gcd(b,a\%b)=bx'+(a\%b)y' gcd(a,b)=gcd(b,a%b)=bx′+(a%b)y′ -
然后可以回推,我们将上式化简得:
g c d ( a , b ) = b x ′ + ( a % b ) y ′ = b x ′ + ( a − ⌊ a / b ⌋ ∗ b ) y ′ gcd(a,b)=bx'+(a\%b)y'=bx'+(a-\lfloor{a/b}\rfloor*b)y' gcd(a,b)=bx′+(a%b)y′=bx′+(a−⌊a/b⌋∗b)y′
g c d ( a , b ) = b x ′ + a y ′ − ⌊ a / b ⌋ ∗ b y ′ gcd(a,b)=bx'+ay'-\lfloor{a/b}\rfloor*by' gcd(a,b)=bx′+ay′−⌊a/b⌋∗by′
其中, ⌊ a / b ⌋ \lfloor{a/b}\rfloor ⌊a/b⌋除法指整除。 -
把含b的因式提取一个b,可得:
g c d ( a , b ) = a y ′ + b ( x ′ − ⌊ a / b ⌋ ∗ y ′ ) gcd(a,b)=ay'+b(x'-\lfloor{a/b}\rfloor*y') gcd(a,b)=ay′+b(x′−⌊a/b⌋∗y′)
又已知: g c d ( a , b ) = a x + b y gcd(a,b)=ax+by gcd(a,b)=ax+by
故 x = y ′ x=y' x=y′, y = x ′ − ⌊ a / b ⌋ ∗ y ′ y=x'-\lfloor{a/b}\rfloor*y' y=x′−⌊a/b⌋∗y′ -
上述思想是递归定义的,不断地利用gcd(a,b) =gcd(b,a%b),到b=0(y的系数为0)时,有x=1、y=0时成立。
-
再根据公式 g c d ( b , a % b ) = g c d ( a , b ) gcd(b,a\%b)=gcd(a,b) gcd(b,a%b)=gcd(a,b)不断回推,并且每次回推因此,其对应的x,y值都要按 x = y ′ x=y' x=y′, y = x ′ − ⌊ a / b ⌋ ∗ y ′ y=x'-\lfloor{a/b}\rfloor*y' y=x′−⌊a/b⌋∗y′的关系做改变。根据解之间的关系,最终可以得到方程 a x + b y = g c d ( a , b ) ax+by =gcd(a, b) ax+by=gcd(a,b)的解。
接口:
int extend_gcd(int a,int b,int &x,int &y);
复杂度:O(logN),其中N和a,b同阶
输入:a,b两个整数
输出:a和b的最大公约数和ax+by=gcd(a,b)的一组解(x,y)
核心代码:(好理解,但代码麻烦版)
int extend_gcd( int a, int b, int &x, int &y )//函数返回a,b的最大公约数
{
if( b==0 )
{
x = 1;
y = 0;
return a;
}
else
{
int r = extend_gcd(b,a%b,x,y);
int temp_x=x, temp_y=y;//这里的x,y已经是被上一行代码修改过的x,y(因为传参是传的x,y的引用)
//temp_x,temp_y分别对应“说明”的x',y'
x = temp_y;//对应“说明”荧光部分的 x=y'
y = temp_x-(a/b)*temp_y;//对应“说明”荧光部分的 y=x'-a/b*y'
return r;
}
}
核心代码:(优化版)
int extend_gcd( int a, int b, int &x, int &y )//函数返回a,b的最大公约数
{
if( b==0 )
{
x = 1;
y = 0;
return a;
}
else
{
int r = extend_gcd(b,a%b,y,x);
y -= x*(a/b);
return r;
}
}