青训营X豆包MarsCode 技术训练营题目解析 | 豆包MarsCode AI 刷题

64 阅读5分钟

下面是对Ai刷题的172题的小C数字倍数问题详解

问题描述

小U对数字倍数问题很感兴趣,她想知道在区间l,r内,有多少个数是aa的倍数,或者是bb的倍数,或者是cc的倍数。你需要帮小U计算出这些数的个数。

代码

 import math
 import functools
 ​
 def gcd(a, b):
     while b != 0:
         a, b = b, a % b
     return a
 ​
 def lcm(a, b):
     return a * b // gcd(a, b)
 ​
 def solution(a: int, b: int, c: int, l: int, r: int) -> int:
     def count_multiples(x: int):
         return (r // x) - ((l - 1) // x)
 ​
     lcm_ab = lcm(a, b)
     lcm_ac = lcm(a, c)
     lcm_bc = lcm(b, c)
     lcm_abc = lcm(lcm_ab, c)
 ​
     total = count_multiples(a) + count_multiples(b) + count_multiples(c)
     total -= count_multiples(lcm_ab) + count_multiples(lcm_ac) + count_multiples(lcm_bc)
     total += count_multiples(lcm_abc)
 ​
     return total
 ​
 if __name__ == "__main__":
     print(solution(2, 3, 4, 1, 10) == 7)
     print(solution(5, 7, 11, 15, 100) == 34)
     print(solution(1, 1, 1, 1, 1000) == 1000)

一、思路解析

  1. 求最小公倍数(LCM)的目的

    • 在这个问题中,我们要计算区间([l,r])内是(a)、(b)、(c)的倍数的数的个数。为了避免重复计算,我们需要用到最小公倍数的概念。
    • 例如,一个数可能既是(a)的倍数又是(b)的倍数,如果直接将是(a)的倍数的个数和是(b)的倍数的个数相加,就会重复计算这些既是(a)又是(b)的倍数的数。最小公倍数(lcm(a,b))可以帮助我们找到这些重复的部分并进行修正。
  2. 容斥原理的应用

    • 首先,我们分别计算是(a)、(b)、(c)的倍数的数的个数,即(count_multiples(a))、(count_multiples(b))和(count_multiples(c))。这一步是比较直观的,对于一个数(x),在区间([l,r])内是(x)的倍数的数的个数可以通过((r//x)-((l - 1)//x))来计算。这里(r//x)表示(r)以内(x)的倍数的个数,((l - 1)//x)表示(l)之前(x)的倍数的个数,两者相减就得到区间([l,r])内(x)的倍数的个数。
    • 然后,我们根据容斥原理进行调整。当我们把是(a)、(b)、(c)的倍数的数的个数相加时,那些既是(a)和(b)的倍数(即(lcm(a,b))的倍数)、既是(a)和(c)的倍数(即(lcm(a,c))的倍数)、既是(b)和(c)的倍数(即(lcm(b,c))的倍数)的数被重复计算了,所以要减去。
    • 但是,在减去这些重复部分的时候,那些既是(a)、(b)又是(c)的倍数(即(lcm(lcm(a,b),c)=lcm(a,b,c))的倍数)的数被多减了一次,所以最后要加上(count_multiples(lcm(a,b,c)))。
  3. 函数的功能

    • gcd函数用于计算两个数的最大公约数(Greatest Common Divisor)。它使用欧几里得算法,通过不断将较大数替换为两数相除的余数,直到余数为(0),此时的除数就是最大公约数。
    • lcm函数用于计算两个数的最小公倍数。根据数学公式(lcm(a,b)=\frac{a\times b}{gcd(a,b)}),先求出最大公约数,再计算最小公倍数。
    • count_multiples函数用于计算在区间([l,r])内是某个数(x)的倍数的数的个数。
    • solution函数综合运用了上述函数,按照容斥原理计算出最终结果。

二、解释(这个问题较抽象,但可以简单示意容斥原理)

  1. 单独计算倍数个数

    • 假设我们有三个集合(A)((a)的倍数)、(B)((b)的倍数)、(C)((c)的倍数)。开始时,我们分别计算(|A|)、(|B|)、(|C|),这里(|A|)就相当于(count_multiples(a)),以此类推。
  2. 重复部分的处理

    • 然后我们发现(A\cap B)(既是(a)又是(b)的倍数)、(A\cap C)、(B\cap C)这些部分被重复计算了,所以要从总数中减去(|A\cap B|)(即(count_multiples(lcm(a,b))))、(|A\cap C|)、(|B\cap C|)。
    • 但是在减去这些部分的时候,(A\cap B\cap C)(既是(a)、(b)又是(c)的倍数)被多减了一次,所以最后要加上(|A\cap B\cap C|)(即(count_multiples(lcm(a,b,c))))。

三、代码详解

  1. gcd函数

    • 这个函数实现了计算两个整数(a)和(b)的最大公约数的功能。
    • 例如,当(a = 12),(b = 18)时,开始时(a = 12),(b = 18),进入循环,因为(b\neq0),执行(a,b = b,a%b),此时(a = 18),(b = 12%18 = 12)。下一次循环,(a = 12),(b = 18%12 = 6),再下一次循环,(a = 6),(b = 12%6 = 0),此时循环结束,返回(a = 6),也就是(12)和(18)的最大公约数。
  2. lcm函数

    • 它接受两个整数(a)和(b),并根据公式(lcm(a,b)=\frac{a\times b}{gcd(a,b)})计算它们的最小公倍数。例如,当(a = 4),(b = 6)时,首先调用(gcd(4,6))得到(2),然后(lcm(4,6)=\frac{4\times6}{2}=12)。
  3. count_multiples函数

    • 这个函数接受一个整数(x),用于计算在区间([l,r])内是(x)的倍数的数的个数。例如,当(x = 3),(l = 1),(r = 10)时,(r//x = 10//3 = 3),((l - 1)//x=(1 - 1)//3 = 0),所以返回(3-0 = 3),即(1)到(10)之间(3)的倍数有(3)个((3)、(6)、(9))。
  4. solution函数

    • 首先,它计算(lcm(a,b))、(lcm(a,c))、(lcm(b,c))和(lcm(lcm(a,b),c)),这些都是为了后续根据容斥原理计算做准备。
    • 然后,它按照容斥原理计算总数。先将是(a)、(b)、(c)的倍数的数的个数相加,再减去既是(a)和(b)的倍数、既是(a)和(c)的倍数、既是(b)和(c)的倍数的数的个数,最后加上既是(a)、(b)又是(c)的倍数的数的个数。
    • if __name__ =="__main__"部分,通过一些测试用例来验证solution函数的正确性。例如,当(a = 2),(b = 3),(c = 4),(l = 1),(r = 10)时,调用solution函数应该得到正确的结果,并且通过print(solution(2,3,4,1,10)==7)来验证结果是否为(7)。同样地,对于其他测试用例也进行了类似的验证。