目录
1、核桃的数量(2013年省赛,lanqiaoOJ题号210)
2、等差数列(2019年第十届省赛,lanqiaoOJ题号192)
3、Hankson 的趣味题(lanqiaoOJ题号520)
一、前言
本文讲了最大公约数&最小公倍数的概念和例题。
二、GCD(最大公约数)
1、定义与性质
最大公约数 Greatest Common Divisor (GCD):整数 a 和 b 的 GCD 是指能同时整除 a 和 b 的最大整数,记为 gcd(a,b)。由于 -a 的因子和 a 的因子相同,因此 gcd(a, b)=gcd(lal, lb|)。编码时只关注正整数的最大公约数。
性质:
(1)gcd(a, b) = gcd(a, a+b) = gcd(a, k*a+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)
2、Python函数gcd()
gcd() 不会返回负数 (c++函数 std::__gcd() 可以返回负数)
gcd() 可以带多个参数 (在 3.9 版更改: 添加了对任意数量的参数的支持,之前的版本只支持两个参数。)
from math import *
print(gcd(15,81)) #3
print(gcd(0,44)) #44
print(gcd(0,0)) #0
print(gcd(-6,-15)) #3
print(gcd(-17,289)) #17
print(gcd(17,-289)) #17
print(gcd(48,96,120,688)) #8,不过3.8版本还不支持多参数
3、手写gcd代码
- 手写gcd函数,常用欧几里得算法。
- 辗转相除法求 gcd:
- gcd(a, b) = gcd(b, a mod b)
- 这是最常用的方法,极为高效。
- 设 a>b,辗转相除法的计算复杂度为 O((loga)^3)。 (以2为底)
- 可能输出负数,和库函数不同
辗转相除法:两个正整数a和b(a>b),它们的最大公约数等于 a 除以 b 的余数 c 和 b 之间的最大公约数。比如10和25,25除以10商2余5,那么10和25的最大公约数,等同于10和5的最大公约数。
def gcd(a,b):
if b==0:
return a
else:
return gcd(b,a%b)
print(gcd(15,81)) #3
print(gcd(0,44)) #44
print(gcd(0,0)) #0
print(gcd(-6,-15)) #-3
print(gcd(-17,289)) #17
print(gcd(17,-289)) #-17
#print(gcd(48,96,120,688)) #8,不过3.8版本还不支持多参数
三、LCM(最小公倍数)
1、定义与性质
最小公倍数 LCM(the Least Common Multiple)。
a 和 b 的最小公倍数 lcm(a,b),从算术基本定理推理得到。
算术基本定理:任何大于 1 的正整数 n 都可以唯一分解为有限个素数的乘积:
n=(p1^c1)(p2^c2)...(pm^cm),其中 ci 都是正整数,pi 都是素数且从小到大。
推导 LCM:
设:a=(p1^c1)(p2^c2)...(pm^cm),b=(p1^f1)(p2^f2)...(pm^fm)
那么:gcd(a, b) = p1^min{c1, f1}*p2^min{c2, f2} *...* pm^min{cm, fm}
Icm(a, b) = p1^max{c1, f1}*p2^max{c2, f2} *...* pm^max{cm, fm}
推出:gcd(a,b)*lcm(a, b)=a*b
即:lcm(a, b)=a*b/gcd(a, b)=a/gcd(a, b)*b。
2、Python库函数lcm()
在 Python 新版本 ( Python3.9 才加入了 1 cm) 中有库函数 1cm(),它可以带多个参数。
在 Python 的旧版本中并没有 lcm() 函数,自己写一个 lcm():
>>> from math import *
>>> def lcm(x,y):
return x//gcd(x,y)*y
>>> lcm(2,3)
6
四、例题
1、核桃的数量(2013年省赛,lanqiaoOJ题号210)
【题目描述】
小张是软件项目经理,他带领 3 个开发组。工期紧,今天都在加班呢。为鼓舞士气,小张打算给每个组发一袋核桃(据传言能补脑)。他的要求是:
1.各组的核桃数量必须相同
2.各组内必须能平分核桃 (当然是不能打碎的)
3.尽量提供满足 1,2 条件的最小数量 (节约闹革命嘛)
【输入格式】
输入三个正整数 a, b, c,表示每个组正在加班的人数,用空格分开 (a,b,c<30)
【输出格式】
输出一个正整数,表示每袋核桃的数量。
这就是一道简单题啊!
from math import *
def lcm(x,y):
return x//gcd(x,y)*y
a,b,c=map(int,input().split())
k=lcm(a,b)
print(lcm(k,c))
2、等差数列(2019年第十届省赛,lanqiaoOJ题号192)
【题目描述】
数学老师给小明出了一道等差数列求和的题目。但是粗心的小明忘记了一部分的数列,只记得其中 N 个整数。现在给出这 N 个整数,小明想知道包含这 N 个整数的最短的等差数列有几项?
【输入描述】
输入的第一行包含一个整数 N。 第二行包含 N 个整数 A1, A2, ..., AN。(注意 A1~AN 并不一定是按等差数列中的顺序给出)(对于所有评测用例,2≤N≤100000,0≤Ai≤10^9。)
【输出描述】
输出一个整数表示答案
所有数字间距离最小的间隔是公差吗?
并不是,例如 {2, 5, 7},最小的间隔是 2,但公差不是 2,是 1。
这是 gcd 问题。把 n 个数据排序,计算它们的间隔,对所有间隔做 GCD,结果为公差。
最少数量等于:(最大值-最小值) / 公差 + 1。
from math import *
n=int(input())
a=list(map(int,input().split()))
a.sort()
d=0
for i in range(1,n):
d=gcd(d,a[i]-a[i-1])
if d==0:
print(n)
else:
print(a[-1]-a[0]//d+1)
3、Hankson 的趣味题(lanqiaoOJ题号520)
【题目描述】
在课堂上,老师讲解了如何求两个正整数 c1 和 c2 的最大公约数和最小公倍数。Hankson 认为自己已经熟练地掌握了这些知识,他开始思考一个 “求公约数” 和 “求公倍数” 之类问题的 “逆问题”,这个问题是这样的:已知正整数 a0, a1, b0, b1,设某未知正整数 x 满足:
1.x 和 a0 的最大公约数是 a1;
2.x 和 b0 的最小公倍数是 b1。
Hankson 的 “逆问题” 就是求出满足条件的正整数 x。但稍加思索之后,他发现这样的 x 并不唯一,甚至可能不存在。因此他转而开始考虑如何求解满足条件的 x 的个数。请你帮助他编程求解这个问题。
【输入格式】
输入第一行为一个正整数 n,表示有 n 组输入数据。接下来的 n 行每行一组输入数据,为四个正整数 a0, a1, b0, b1,每两个整数之间用一个空格隔开。输入数据保证 a0 能被 a1 整除,b1 能被 b0 整除。
数据规模:对于 100% 的数据,保证有 1≤a0, a1, b0, b1≤2,000,000,000 且 n≤2000
【输出格式】
输出共 n 行。每组输入数据的输出结果占一行,为一个整数。对于每组数据:
若不存在这样的 x, 请输出0;若存在这样的 x,请输出满足条件的 x 的个数。
1)暴力法
2)优化
若 x 是 b1 的因子,有 x*y=b1,y 也可能是答案。·
只需要在范围 x<=sqrt(b1) 内查询,同时判断 y 就行了。
但还是超时,因为 gcd 计算也要花时间,加上一个优化:if b1%x==0,表示 b1 是 x 的公倍数。
from math import *
def lcm(x,y):
return x//gcd(x,y)*y
n=int(input())
for _ in range(n):
a0,a1,b0,b1=map(int,input().split())
ans=0
for x in range(1,int(sqrt(b1))+1):
if b1%x==0: #表示b1是x的公倍数
if gcd(x,a0)==a1 and lcm(x,b0)==b1:
ans+=1
y=b1//x
if x==y:
continue
if gcd(y,a0)==a1 and lcm(y,b0)==b1:
ans+=1
print(ans)
复杂度?b1≤2,000,000,000
n<=2000
以上,最大公约数&最小公倍数
更多例题见蓝桥网站
祝好