classMathUtils: defgcd(self, a: int, b: int) -> int: """ 使用辗转相除法(欧几里得算法)计算最大公约数。 核心思想: 两个整数的最大公约数等于其中较小的数和两数相除余数的最大公约数。 递归的基准情况是当 b 为 0 时,最大公约数是 a。 """ return a if b == 0elseself.gcd(b, a % b)
utils = MathUtils() a, b = 48, 18 print(f"GCD of {a} and {b} is: {utils.gcd(a, b)}") # 输出: 6 print(f"LCM of {a} and {b} is: {utils.lcm(a, b)}") # 输出: 144
classSolution: defnthMagicalNumber(self, n: int, a: int, b: int) -> int: """ 寻找第 n 个神奇数字。 核心思想: 答案本身具有单调性(越大的数,它前面的神奇数字越多),因此可以使用二分查找来寻找答案。 1. 定义查找范围:下界 `l=0`,上界 `r` 可以是一个足够大的数,例如 `n * min(a, b)`。 2. 二分中点为 `m`,我们需要快速计算出 `1` 到 `m` 之间有多少个神奇数字。 3. 这个数量可以通过容斥原理计算:`count = m/a + m/b - m/lcm(a, b)`。 其中 `lcm` 是 a 和 b 的最小公倍数。 4. 如果 `count >= n`,说明第 `n` 个神奇数字可能就是 `m` 或者更小,所以我们将 `m` 存为候选答案,并向左查找 `r = m - 1`。 5. 如果 `count < n`,说明 `m` 太小了,第 `n` 个神奇数字在 `m` 的右边,所以向右查找 `l = m + 1`。 6. 二分结束后,候选答案即为所求,最后对结果取模。 """ lcm_val = self._lcm(a, b) ans = 0 mod = 1000000007 # l = 0, r = n * min(a, b) 是一个安全上界 l, r = 0, n * min(a, b) while l <= r: m = (l + r) // 2 # 计算 1...m 中有多少个数是 a 或 b 的倍数 # 使用容斥原理:(m中a的倍数) + (m中b的倍数) - (m中a和b公倍数的个数) if m // a + m // b - m // lcm_val >= n: ans = m r = m - 1 else: l = m + 1 return ans % mod
def_gcd(self, a: int, b: int) -> int: return a if b == 0elseself._gcd(b, a % b)
def_lcm(self, a: int, b: int) -> int: return (a * b) // self._gcd(a, b)
算法分析
时间复杂度:$O(\log(\text{n} \times \min(a, b)))$
空间复杂度:$O(\log(\min(a, b)))$(递归调用栈)
核心技巧:二分答案法 + 容斥原理
核心知识点三:同余原理
基本公理
设 $m$ 为正整数(模),若:
$a \equiv b \pmod{m}$
$c \equiv d \pmod{m}$
则有:
加法:$a + c \equiv b + d \pmod{m}$
减法:$a - c \equiv b - d \pmod{m}$
乘法:$ac \equiv bd \pmod{m}$
核心思想
在模运算中,我们可以在每一步计算后都取模,这样可以:
防止中间结果溢出
保持计算结果的正确性
提高计算效率
实际应用
问题:计算 $((a + b) \times (c - d) + (a \times c - b \times d)) \bmod m$
classSolution: deff1(self, a: int, b: int, c: int, d: int, mod: int) -> int: """ 使用 Python 的原生大整数计算,等同于 Java 的 BigInteger 版本。 核心思想: 直接进行数学运算,让语言自动处理中间过程中可能出现的巨大数值。 最后对最终结果取模。 """ o5 = a + b o6 = c - d o7 = a * c o8 = b * d o9 = o5 * o6 o10 = o7 - o8 o11 = o9 + o10 # (res % mod + mod) % mod 是一个确保结果为正的通用技巧。 return (o11 % mod + mod) % mod
deff2(self, a: int, b: int, c: int, d: int, mod: int) -> int: """ 使用同余原理在每一步运算后都取模。 核心思想 (同余原理): - (A + B) % M = ((A % M) + (B % M)) % M - (A - B) % M = ((A % M) - (B % M) + M) % M (加上M确保结果非负) - (A * B) % M = ((A % M) * (B % M)) % M 这种方法可以保证所有中间计算结果都在一个可控的范围内,避免大数运算,效率更高。 """ o1 = a % mod o2 = b % mod o3 = c % mod o4 = d % mod o5 = (o1 + o2) % mod # 加上 mod 再取模,是为了防止 o3 < o4 时出现负数 o6 = (o3 - o4 + mod) % mod o7 = (o1 * o3) % mod o8 = (o2 * o4) % mod o9 = (o5 * o6) % mod o10 = (o7 - o8 + mod) % mod ans = (o9 + o10) % mod return ans
重要注意事项
减法处理:$(a - b) \bmod m = ((a \bmod m) - (b \bmod m) + m) \bmod m$
if __name__ == '__main__': s = Solution() print("测试开始") test_time = 100000 mod = 1000000007 all_passed = True for i inrange(test_time): a = random_long() b = random_long() c = random_long() d = random_long() if s.f1(a, b, c, d, mod) != s.f2(a, b, c, d, mod): print("出错了!") all_passed = False break if all_passed: print("测试结束,全部通过!")
算法技巧总结
1. 欧几里得算法模板
1 2 3 4 5
defgcd(a, b): return a if b == 0else gcd(b, a % b)
deflcm(a, b): return (a * b) // gcd(a, b)
2. 二分答案法模板
1 2 3 4 5 6 7 8 9 10
defbinary_search_answer(check_function, left, right): ans = -1 while left <= right: mid = (left + right) // 2 if check_function(mid): ans = mid right = mid - 1# 继续寻找更小的答案 else: left = mid + 1 return ans
3. 容斥原理(两个集合)
1 2 3 4
# |A ∪ B| = |A| + |B| - |A ∩ B| definclusion_exclusion_two_sets(n, a, b): lcm_ab = lcm(a, b) return n // a + n // b - n // lcm_ab
4. 同余运算模板
1 2 3 4 5 6 7 8 9 10 11
defsafe_mod_operations(a, b, mod): # 加法 add_result = (a + b) % mod # 减法(确保非负) sub_result = (a - b + mod) % mod # 乘法 mul_result = (a * b) % mod return add_result, sub_result, mul_result
defwin2(self, n: int) -> str: """ 通过暴力解的输出,发现胜负结果以5为周期循环 核心思想: 观察 `win1` 的输出,可以发现: n=0 -> B (先手输) n=1 -> A (先手赢) n=2 -> B (先手输) n=3 -> A (先手赢) n=4 -> A (先手赢) n=5 -> B (先手输) ... 胜负状态是 (B, A, B, A, A),以5为周期。 当 n % 5 的结果是 0 或 2 时,先手方 A 面对的是必败局面,所以 B 赢。 否则 A 赢。 """ if n % 5 == 0or n % 5 == 2: return"B" else: return"A"
规律分析
通过打表观察:
1 2 3 4 5 6 7 8 9 10 11 12 13
草量 : 赢家 0 : B 1 : A 2 : B 3 : A 4 : A 5 : B 6 : A 7 : B 8 : A 9 : A 10 : B ...
defrecursive_with_memo(params): memo = {} defhelper(state): if state in memo: return memo[state] # base case if is_base_case(state): return base_result # 递归计算 result = calculate_from_subproblems(state) memo[state] = result return result return helper(initial_state)