小C的数字倍数问题 | 豆包MarsCode AI刷题

97 阅读4分钟

解决区间内倍数个数问题:包含-排除原理的应用

在编程竞赛和算法题中,类似于“在区间 [l, r] 内有多少个数是某些数字的倍数”的问题常常出现。通常来说,直接暴力遍历区间内所有数字会非常低效,尤其当区间非常大时。这时,我们需要使用一些数学技巧来优化计算,包含-排除原理就是其中之一。

本文将通过一个具体的例子,详细介绍如何使用包含-排除原理高效地计算区间内满足某些条件的倍数数量。

问题描述:

给定一个区间 [l, r] 和三个整数 abc,我们需要计算在区间内有多少个数字是 abc 的倍数。

例如,假设 a=2b=3c=4,并且区间为 [1, 10]。我们需要计算在区间 [1, 10] 内,能够被 234 整除的数字个数。

解决思路:

  1. 倍数计算:

    首先,我们需要计算区间 [l, r] 内,数字 abc 各自的倍数个数。通过整数除法可以轻松得到某个数字的倍数个数。例如,区间 [1, r] 内,数字 n 的倍数个数是 r // n。因此,区间 [l, r]n 的倍数个数可以通过以下公式计算:

    count(n)=rn−l−1n\text{count}(n) = \frac{r}{n} - \frac{l-1}{n}

    这个式子通过减去 [1, l-1] 区间内的倍数个数,从而获得 [l, r] 区间内的倍数个数。

  2. 包含-排除原理:

    直接计算 abc 的倍数个数会导致重复计算。例如,某个数既是 a 的倍数,又是 b 的倍数,它会被分别计算一次。为了避免重复计算,我们使用包含-排除原理:

    • 计算 abc 各自的倍数个数。
    • 减去同时是 ab 的倍数的个数,减去同时是 bc 的倍数的个数,减去同时是 ca 的倍数的个数。
    • 需要加上同时是 abc 的倍数的个数。

    通过这种方式,我们能够确保每个倍数仅计算一次,避免了重复计数。

  3. 最小公倍数(LCM)的计算:

    在包含-排除原理中,我们需要计算两个数字的最小公倍数(LCM),因为求交集(即同时是 ab 的倍数)需要用到最小公倍数。最小公倍数的计算可以通过以下公式:

    lcm(x,y)=x×ygcd(x,y)\text{lcm}(x, y) = \frac{x \times y}{\text{gcd}(x, y)}

    其中,gcd(x, y) 表示 xy 的最大公约数。

代码实现:

import math

def count_multiples(n, l, r):
    """计算在范围 [l, r] 内 n 的倍数个数"""
    return r // n - (l - 1) // n

def lcm(x, y):
    """计算 x 和 y 的最小公倍数"""
    return (x * y) // math.gcd(x, y)

def solution(a: int, b: int, c: int, l: int, r: int) -> int:
    # 计算每个数的倍数个数
    count_a = count_multiples(a, l, r)
    count_b = count_multiples(b, l, r)
    count_c = count_multiples(c, l, r)
    
    # 使用容斥原理计算重叠的倍数个数
    count_ab = count_multiples(lcm(a, b), l, r)
    count_bc = count_multiples(lcm(b, c), l, r)
    count_ca = count_multiples(lcm(c, a), l, r)
    
    count_abc = count_multiples(lcm(a, lcm(b, c)), l, r)
    
    # 根据容斥原理计算最终结果
    result = (count_a + count_b + count_c - count_ab - count_bc - count_ca + count_abc)
    
    return result

# 测试样例
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. count_multiples(n, l, r) 这个函数用于计算区间 [l, r] 内,数字 n 的倍数个数。通过整数除法得出倍数个数,时间复杂度为 O(1)。
  2. lcm(x, y) 计算数字 xy 的最小公倍数(LCM),使用了最大公约数(GCD)来优化计算。时间复杂度为 O(log(min(x, y)))。
  3. solution(a, b, c, l, r) 主要函数,通过调用 count_multipleslcm 函数来实现包含-排除原理的计算。最终返回结果即为符合条件的数字个数。时间复杂度为 O(log(min(a, b, c))),这是因为计算最小公倍数和最大公约数的时间复杂度为 O(log(x))。

时间复杂度与空间复杂度:

  • 时间复杂度:

    • 计算倍数的个数是 O(1),
    • 计算最小公倍数和最大公约数是 O(log(x)),其中 x 是数值的大小,
    • 所以总时间复杂度是 O(log(max(a, b, c)))。
  • 空间复杂度:

    • 只用了常数空间来存储临时变量,因此空间复杂度是 O(1)。

总结:

通过合理使用 包含-排除原理 和最小公倍数的计算,我们能够高效地解决区间内倍数个数的问题。这个方法避免了暴力枚举每个数,从而显著提高了效率,特别适用于大范围的区间计算。