算法竞赛专题解析(20):数论--GCD和LCM

本系列文章将于2021年整理出版。前驱教材:《算法竞赛入门到进阶》 清华大学出版社
网购:京东 当当   作者签名书:点我
公众号同步:算法专辑   
暑假福利:胡说三国
有建议请加QQ 群:567554289

文章目录

  • 1. GCD定义
  • 2. GCD性质
  • 3. GCD编码
    • 3.1 欧几里得算法
    • 3.2 更相减损术
    • 3.3 Stein算法
  • 4. LCM
  • 5. 例题
  • 6. 习题

   最大公约数1和最小公倍数(Least Common Multiple)是竞赛中频繁出现的考点。

1. GCD定义

   整数a和b的最大公因数是指能同时整除a和b的最大整数,记为gcd(a, b)。
   例如:gcd(15, 81) = 3,gcd(0, 44) = 44,gcd(0, 0) = 0,gcd(-6, -15) = 3,gcd(-17,289) = 17。
   注意:由于-a的因子和a的因子相同,因此gcd(a, b) = gcd(|a|, |b|)。编码时只需要关注正整数的最大公因数。

2. GCD性质

  (1)gcd(a,b) = gcd(a, a+b) = gcd(a, ka+b)
  (2)gcd(ka, kb) = k·gcd(a, b)
  (3)定义多个整数的最大公约数:gcd(a, b, c) = gcd(gcd(a, b), c)
  (4)若gcd(a, b) = d,则gcd(a/d, b/d) = 1,即a/d与b/d互素。这个定理很重要
  (5)gcd(a+cb, b) = gcd(a, b)

3. GCD编码

  编程时可以直接用c++函数std::__gcd(a, b)。注意:参数a和b都应该是正整数,否则可能会返回负数。下面介绍三种算法。

3.1 欧几里得算法

  用辗转相除法求gcd,即gcd(a, b) = gcd(b, a mod b)。代码是:

int gcd(int a, int b){  // 一般要求a>=0, b>0。若a=b=0,代码也正确,返回0
	return b? gcd(b, a%b):a;
}

  这是竞赛中最常用的编码,它极为高效,“拉梅定理”给出了复杂度分析。
  拉梅定理:用欧几里得算法计算两个正整数的最大公因数,需要的除法次数不会超过两个整数中较小的哪个十进制数的位数的5倍。
  推论:用欧几里得算法求gcd(a, b),a > b,需要 O ( ( l o g 2 a ) 3 ) O((log_2a)^3) O((log2a)3)次位运算。
  欧几里得算法的缺点是需要做除法取模运算,而高精度大数的除法比较耗时,此时可以用“更相减损术”2和stein算法,它们只用到了减法和移位操作。

  不过,在竞赛中不太可能直接使用“更相减损术”和stein算法求最大公约数。下面的介绍,只是为了帮助读者理解GCD的性质。

3.2 更相减损术

  计算基于这一性质:gcd(a, b) = gcd(b, a-b) = gcd(a, a-b)。

  计算步骤:用较大的数减较小的数,把所得的差与较小的数比较,然后继续做减法操作,直到减数和差相等为止。
  编码也很简单:

int gcd(int a, int b){
   while(a != b){   //a==b时结束计算
	   if(a > b)  a = a - b;
	   else       b = b - a;
   }
   return a;
}

  更相减损术虽然避免了欧几里得的取模计算,但是计算次数比欧几里得算法多很多,极端情况下需要计算 O ( m a x ( a , b ) ) O(max(a, b)) O(max(a,b))次,例如a = 100,b = 1时,需计算100次。

3.3 Stein算法

  它是更相减损术的改进。求gcd(a, b)时,可以分为几个情况进行优化:
  1)a、b都是偶数。gcd(a, b) = 2gcd(a/2, b/2),计算减半。
  2)a奇b偶(或a偶b奇)。根据这一原理:若k与y互为质数,有gcd(kx, y) = gcd(x, b)。k = 2时,有gcd(a, b) = gcd(a/2, b),即偶数减半。
  3)a、b都是奇数。gcd(a, b ) = gcd( (a+b)/2, (a-b)/2 )。
  算法的结束条件仍然是gcd(a, a) = a。
  除2操作用移位就可以了,所以Stein算法只用到加减法和移位。
  关于高精度大数的GCD计算,读者可以用下面这一题练习,类似的题有hdu 5050。


SuperGCD 洛谷 P2152
题目描述:求2个正整数a,b的最大公约数,0 < a, b ≤ 1 0 10000 10^{10000} 1010000


  不过,由于OJ都支持java,比赛的时候直接用java函数处理大数是最简单的:

//洛谷 P2152的java代码
import java.math.*;
import java.util.*;
public class Main {
	public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
		BigInteger a = in.nextBigInteger();
		BigInteger b = in.nextBigInteger();
		System.out.println(a.gcd(b));
	}
}

4. LCM

  a和b的最小公倍数lcm(a, b),可以从算术基本定理推理得到。
  算术基本定理(唯一分解定理):任何大于1的正整数n都可以唯一分解为有限个素数的乘积: n = p 1 c 1 ⋅ p 2 c 2 . . . ⋅ p m c m n = p_1^{c_1}·p_2^{c_2}...·p_m^{c_m} n=p1c1p2c2...pmcm,其中 c i c_i ci都是正整数, p i p_i pi都是素数且从小到大。
  设: a = p 1 c 1 ⋅ p 2 c 2 . . . ⋅ p m c m a = p_1^{c_1}·p_2^{c_2}...·p_m^{c_m} a=p1c1p2c2...pmcm b = p 1 f 1 ⋅ p 2 f 2 . . . ⋅ p m f m b = p_1^{f_1}·p_2^{f_2}...·p_m^{f_m} b=p1f1p2f2...pmfm
  那么:
     g c d ( a , b ) = p 1 m i n { c 1 , f 1 } ⋅ p 2 m i n { c 2 , f 2 } . . . ⋅ p m m i n { c m , f m } gcd(a, b) = p_1^{min\{c_1,f_1\}}·p_2^{min\{c2,f2\}}...·p_m^{min\{cm,fm\}} gcd(a,b)=p1min{c1,f1}p2min{c2,f2}...pmmin{cm,fm}
     l c m ( a , b ) = p 1 m a x { c 1 , f 1 } ⋅ p 2 m a x { c 2 , f 2 } . . . ⋅ p m m a x { c m , f m } lcm(a, b) = p_1^{max\{c_1,f_1\}}·p_2^{max\{c2,f2\}}...·p_m^{max\{cm,fm\}} lcm(a,b)=p1max{c1,f1}p2max{c2,f2}...pmmax{cm,fm}
  推出:
     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)=ab
  即:
     l c m ( a , b ) = a ∗ b / g c d ( a , b ) = a / g c d ( a , b ) ∗ b lcm(a,b)= a*b/gcd(a, b) = a/gcd(a, b)*b lcm(a,b)=ab/gcd(a,b)=a/gcd(a,b)b
  注意先做除法再做乘法,如果先做乘法可能会溢出3

int lcm(int a, int b){ 
	return a / gcd(a, b) * b;
}

5. 例题

  (1)hdu 5019
  题目描述:给出整数x、y、k,求x、y的第k大公约数。
  题解:先求最大公因数d = gcd(x, y),由于其他公因子都是d的因子,那么从1到 d \sqrt{d} d (注意不需要到d)逐个检查是否能整除d,即可找到所有公因子。

  (2)hdu 2503
  题目描述:给出2个分数a/b和c/d,求a/b + c/d,要求是最简形式。
  题解:a/b + c/d = (ad + bc) / bd,分子和分母除以两者的最大公约数。

   (3)hdu 2504
  题目描述:已知a和b,求满足gcd(a,c) = b的最小的c。
  题解:暴力搜b到a*b内符合条件的c。

  (4)hdu 4497
  题目描述:给定两个正整数G、L,问满足gcd(x, y, z) = G和lcm(x, y, z) = L的(x, y, z)有多少个?注意,(1, 2, 3)和(1, 3, 2)是不同的。
  题解。此题利用了GCD的几个性质:
  1)若gcd(a, b) = d,则gcd(a/d, b/d) = 1,即a/d与b/d互素;
  2) g c d ( a , b ) = p 1 m i n { c 1 , f 1 } ⋅ p 2 m i n { c 2 , f 2 } . . . ⋅ p m m i n { c m , f m } gcd(a, b) = p_1^{min\{c_1,f_1\}}·p_2^{min\{c2,f2\}}...·p_m^{min\{cm,fm\}} gcd(a,b)=p1min{c1,f1}p2min{c2,f2}...pmmin{cm,fm}
  3) l c m ( a , b ) = p 1 m a x { c 1 , f 1 } ⋅ p 2 m a x { c 2 , f 2 } . . . ⋅ p m m a x { c m , f m } lcm(a, b) = p_1^{max\{c_1,f_1\}}·p_2^{max\{c2,f2\}}...·p_m^{max\{cm,fm\}} lcm(a,b)=p1max{c1,f1}p2max{c2,f2}...pmmax{cm,fm}
  若L % G ≠ 0,显然无解。下面分析L % G = 0的情况。
  把问题转化为:满足gcd(x/G, y/G, z/G) = 1和lcm(x/G,y/G,z/G)= L/G的(x/G, y/G, z/G)有多少个。下面用排列组合分析有多少种情况。
  根据算术基本定理,把x/G、y/G、z/G写成:
    x/G = p 1 i 1 ⋅ p 2 i 2 ⋅ p 3 i 3 p_1^{i_1}·p_2^{i_2}·p_3^{i_3} p1i1p2i2p3i3
    y/G = p 1 j 1 ⋅ p 2 j 2 ⋅ p 3 j 3 p_1^{j_1}·p_2^{j_2}·p_3^{j_3} p1j1p2j2p3j3
    z/G = p 1 k 1 ⋅ p 2 k 2 ⋅ p 3 k 3 p_1^{k_1}·p_2^{k_2}·p_3^{k_3} p1k1p2k2p3k3
  式子要满足x/G、y/G、z/G互素的条件。以{ i 1 , j 1 , k 1 i_1, j_1, k_1 i1,j1,k1}为例,其中至少有1个应该等于0。
  另外,把L/G写成:
    L/G = p 1 t 1 ⋅ p 2 t 2 ⋅ p 3 t 3 p_1^{t_1}·p_2^{t_2}·p_3^{t_3} p1t1p2t2p3t3
  式子要满足lcm(x/G,y/G,z/G)= L/G。以{ i 1 , j 1 , k 1 i_1, j_1, k_1 i1,j1,k1}为例,允许的情况是:
  1){0, 0, t 1 t_1 t1},有三种排列;
  2){0, t 1 t_1 t1, t 1 t_1 t1},有三种排列;
  3){0, t 1 t_1 t1, 1 ~ t 1 t_1 t1-1},有( t 1 t_1 t1-1)*6种排列;
  加起来一共有 t 1 t_1 t1*6 种排列。
  最后问题转化为求 t 1 t_1 t1 t 2 t_2 t2 t 3 t_3 t3,即分解 L/G 的质因子。

6. 习题

  GCD的题目一般和其他知识点结合出题。
  hdu 2104,互素判定
  hdu 3092,GCD+完全背包
  hdu 5970,GCD+循环节
  hdu 5584,LCM问题
  洛谷 P2568 ,GCD + 莫比乌斯反演
  洛谷 P2398,GCD求和
  洛谷 P1890,询问区间内的GCD
  poj 1722,GCD思维题
  poj 2685,GCD+快速幂
  poj 3101,大数GCD
  poj 2429,GCD + Rabin-Miller测试


  1. 最大公约数有多种英文表述:Greatest Common Divisor(GCD)、Greatest Common Denominator、Greatest Common Factor(GCF)、Highest Common Factor (HCF)。 ↩︎

  2. 欧几里得《几何原本》是公元前三世纪的著作,中国《九章算术》成于公元一世纪,流行本是三世纪刘徽的注本,都非常古老。“更相减损术”在《九章算术》卷一的“约分”这一节:“可半者半之,不可半者,副置分母、子之数,以少减多,更相减损,求其等也。以等数约之。”本文给出的代码省去了a、b为偶数的情况,即“可半者半之”。 ↩︎

  3. 参考:《算法竞赛入门经典(第2版)》,刘汝佳,清华大学出版社,312页。 ↩︎

你可能感兴趣的:(算法竞赛专题解析(20):数论--GCD和LCM)