diff --git a/solutions/0029.DistinctPowers/README.md b/solutions/0029.DistinctPowers/README.md new file mode 100644 index 0000000..c1b7714 --- /dev/null +++ b/solutions/0029.DistinctPowers/README.md @@ -0,0 +1,10 @@ +# Distinct Powers + +只需要比较所有可能重复的底和幂,找到有多少这样的a^b就能知道有多少重复。 +这个逻辑最为简单,我自己的实现免费处理较大额的底和幂的情况,这点我暂时没想到好方法。 + +当看到 [WP(Page 5)](https://projecteuler.net/post_id=92910) 的方法,我才明白自己的问题在哪。 +这类问题真的是,单纯解出来不算什么,如何使用数学更简单更快捷的解出来,才是难的。 + +核心关键点是组合数学的[容斥原理](https://zh.wikipedia.org/wiki/%E6%8E%92%E5%AE%B9%E5%8E%9F%E7%90%86)。 +因为幂的数学特点,可能需要多次应用容斥原理,以确保不重复计算。这也是我自己方法和WP方法的差距所在。 diff --git a/solutions/0029.DistinctPowers/euler_29.py b/solutions/0029.DistinctPowers/euler_29.py new file mode 100644 index 0000000..4eb93d8 --- /dev/null +++ b/solutions/0029.DistinctPowers/euler_29.py @@ -0,0 +1,92 @@ +""" +Consider all integer combinations of a^b for 2<=a<=5 and 2<=b<=5 : + +2^2 = 4 2^3 = 8 2^4 = 16 2^5 = 32 +3^2 = 9 3^3 = 27 3^4 = 81 3^5 = 243 +4^2 = 16 4^3 = 64 4^4 = 256 4^5 = 1024 +5^2 = 25 5^3 = 125 5^4 = 625 5^5 = 3125 + +If they are then placed in numerical order, with any repeats removed, +we get the following sequence of 15 distinct terms: + +4, 8, 9, 16, 25, 27, 32, 64, 81, 125, 243, 256, 625, 1024, 3125 + +How many distinct terms are in the sequence generated by a^b for 2<=a<=100 and 2<=b<=100 ? +""" + +import time + + +def timer(func): + def wrapper(*args, **kwargs): + start_time = time.time() + result = func(*args, **kwargs) + end_time = time.time() + print(f"{func.__name__} runtime: {end_time - start_time} seconds") + return result + + return wrapper + + +def count_distinct_terms_efficient(limit_a, limit_b): + # 预计算每个 a 的最小底数表示 + base_map = {} + power_map = {} + + for a in range(2, limit_a + 1): + base_map[a] = a + power_map[a] = 1 + + # 找出所有可以表示为幂的数 + for power in range(2, 7): # 2^7=128>100,所以最多到6次幂 + base = 2 + while base**power <= limit_a: + value = base**power + # 如果这个值还没有被更小的底数表示 + if base_map[value] == value: + base_map[value] = base + power_map[value] = power + base += 1 + + # 使用集合存储 (base, exponent) 对 + seen = set() + + for a in range(2, limit_a + 1): + base = base_map[a] + power = power_map[a] + + # 如果 a 是某个数的幂,我们需要检查 base 是否还能分解 + # 例如:16 = 4^2,但 4 = 2^2,所以 16 = 2^4 + # 我们需要找到最终的 base + while base_map[base] != base: + power = power * power_map[base] + base = base_map[base] + + for b in range(2, limit_b + 1): + seen.add((base, power * b)) + + return len(seen) + + +def do_compute(a_top: int, b_top: int, a_down: int = 2, b_down: int = 2) -> int: + tmp = set() + for a in range(a_down, a_top + 1): + for b in range(b_down, b_top + 1): + tmp.add(a**b) + return len(tmp) + + +@timer +def main(): + """not bad""" + print(count_distinct_terms_efficient(100, 100)) + + +@timer +def main2(): + print(do_compute(100, 100)) + + +if __name__ == "__main__": + main() + main2() diff --git a/solutions/0029.DistinctPowers/euler_29_best.py b/solutions/0029.DistinctPowers/euler_29_best.py new file mode 100644 index 0000000..8516af8 --- /dev/null +++ b/solutions/0029.DistinctPowers/euler_29_best.py @@ -0,0 +1,134 @@ +import math +import time +from functools import lru_cache +from typing import Callable + + +def timer(func: Callable) -> Callable: + def wrapper(*args, **kwargs): + start_time = time.perf_counter() + result = func(*args, **kwargs) + end_time = time.perf_counter() + elapsed = end_time - start_time + print(f"Function {func.__name__} execution time: {elapsed:.4f} seconds") + return result + + return wrapper + + +def maxpower(a: int, n: int) -> int: + """计算最大的整数c,使得a^c ≤ n""" + res = int(math.log(n) / math.log(a)) + + # 处理边界情况 + if pow(a, res + 1) <= n: + res += 1 + if pow(a, res) > n: + res -= 1 + + return res + + +@lru_cache(maxsize=None) +def lcm(a: int, b: int) -> int: + """计算最小公倍数(使用缓存优化)""" + gcd_val = math.gcd(a, b) + return a // gcd_val * b + + +def recurse( + lc: int, index: int, sign: int, left: int, right: int, thelist: list[int] +) -> int: + """容斥原理的递归实现""" + if lc > right: + return 0 + + res = sign * (right // lc - (left - 1) // lc) + + # 递归处理剩余元素 + for i in range(index + 1, len(thelist)): + res += recurse(lcm(lc, thelist[i]), i, -sign, left, right, thelist) + + return res + + +def dd(left: int, right: int, a: int, b: int, check: list[bool]) -> int: + """双层容斥计算""" + res = right // b - (left - 1) // b + thelist = [i for i in range(a, b) if check[i]] + + for i in range(len(thelist)): + res -= recurse(lcm(b, thelist[i]), i, 1, left, right, thelist) + + return res + + +def compute_counts(n: int) -> tuple[list[int], int, int]: + """计算前缀和数组""" + sqn = int(math.isqrt(n)) # 使用isqrt替代sqrt,返回整数 + maxc = maxpower(2, n) + + # 初始化数组 + counts = [0] * (maxc + 1) + counts[1] = n - 1 + + # 主计算循环 + for c in range(2, maxc + 1): + check = [True] * (maxc + 1) + umin = (c - 1) * n + 1 + umax = c * n + + # 优化筛法:使用步长跳过非倍数 + for i in range(c, maxc // 2 + 1): + check[i * 2 : maxc + 1 : i] = [False] * ((maxc - i * 2) // i + 1) + + # 只处理质数(check[f]为True) + for f in range(c, maxc + 1): + if check[f]: + counts[f] += dd(umin, umax, c, f, check) + + # 计算前缀和 + for c in range(2, maxc + 1): + counts[c] += counts[c - 1] + + return counts, sqn, maxc + + +def compute_final_answer(n: int) -> int: + """计算最终答案""" + counts, sqn, _ = compute_counts(n) + + ans = 0 + coll = 0 + used = [False] * (sqn + 1) + + # 统计答案 + for i in range(2, sqn + 1): + if not used[i]: + c = maxpower(i, n) + ans += counts[c] + + u = i + for j in range(2, c + 1): + u *= i + if u <= sqn: + used[u] = True + else: + coll += c - j + 1 + break + + # 最终调整 + ans += (n - sqn) * (n - 1) + ans -= coll * (n - 1) + + return ans + + +@timer +def main(n: int = 10**6) -> None: + answer = compute_final_answer(n) + print(f"n = {n}, Answer = {answer}") + + +if __name__ == "__main__": + main(10**7)