解决区间内倍数个数问题:包含-排除原理的应用
在编程竞赛和算法题中,类似于“在区间 [l, r] 内有多少个数是某些数字的倍数”的问题常常出现。通常来说,直接暴力遍历区间内所有数字会非常低效,尤其当区间非常大时。这时,我们需要使用一些数学技巧来优化计算,包含-排除原理就是其中之一。
本文将通过一个具体的例子,详细介绍如何使用包含-排除原理高效地计算区间内满足某些条件的倍数数量。
问题描述:
给定一个区间 [l, r] 和三个整数 a、b、c,我们需要计算在区间内有多少个数字是 a、b 或 c 的倍数。
例如,假设 a=2,b=3,c=4,并且区间为 [1, 10]。我们需要计算在区间 [1, 10] 内,能够被 2、3 或 4 整除的数字个数。
解决思路:
-
倍数计算:
首先,我们需要计算区间
[l, r]内,数字a、b和c各自的倍数个数。通过整数除法可以轻松得到某个数字的倍数个数。例如,区间[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]区间内的倍数个数。 -
包含-排除原理:
直接计算
a、b、c的倍数个数会导致重复计算。例如,某个数既是a的倍数,又是b的倍数,它会被分别计算一次。为了避免重复计算,我们使用包含-排除原理:- 计算
a、b、c各自的倍数个数。 - 减去同时是
a和b的倍数的个数,减去同时是b和c的倍数的个数,减去同时是c和a的倍数的个数。 - 需要加上同时是
a、b和c的倍数的个数。
通过这种方式,我们能够确保每个倍数仅计算一次,避免了重复计数。
- 计算
-
最小公倍数(LCM)的计算:
在包含-排除原理中,我们需要计算两个数字的最小公倍数(LCM),因为求交集(即同时是
a和b的倍数)需要用到最小公倍数。最小公倍数的计算可以通过以下公式:lcm(x,y)=x×ygcd(x,y)\text{lcm}(x, y) = \frac{x \times y}{\text{gcd}(x, y)}
其中,
gcd(x, y)表示x和y的最大公约数。
代码实现:
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
代码解析:
count_multiples(n, l, r): 这个函数用于计算区间[l, r]内,数字n的倍数个数。通过整数除法得出倍数个数,时间复杂度为 O(1)。lcm(x, y): 计算数字x和y的最小公倍数(LCM),使用了最大公约数(GCD)来优化计算。时间复杂度为 O(log(min(x, y)))。solution(a, b, c, l, r): 主要函数,通过调用count_multiples和lcm函数来实现包含-排除原理的计算。最终返回结果即为符合条件的数字个数。时间复杂度为 O(log(min(a, b, c))),这是因为计算最小公倍数和最大公约数的时间复杂度为 O(log(x))。
时间复杂度与空间复杂度:
-
时间复杂度:
- 计算倍数的个数是 O(1),
- 计算最小公倍数和最大公约数是 O(log(x)),其中
x是数值的大小, - 所以总时间复杂度是 O(log(max(a, b, c)))。
-
空间复杂度:
- 只用了常数空间来存储临时变量,因此空间复杂度是 O(1)。
总结:
通过合理使用 包含-排除原理 和最小公倍数的计算,我们能够高效地解决区间内倍数个数的问题。这个方法避免了暴力枚举每个数,从而显著提高了效率,特别适用于大范围的区间计算。