diff --git a/solutions/0047.PrimesFactors/euler_47.py b/solutions/0047.PrimesFactors/euler_47.py new file mode 100644 index 0000000..b59cc50 --- /dev/null +++ b/solutions/0047.PrimesFactors/euler_47.py @@ -0,0 +1,168 @@ +""" +The first two consecutive numbers to have two distinct prime factors are: + +14 = 2 * 7 +15 = 3 * 5 + +The first three consecutive numbers to have three distinct prime factors are: + +644 = 2^2 * 7 * 23 +645 = 3 * 5 * 43 +646 = 2 * 17 * 19 + +Find the first four consecutive integers to have four distinct prime factors each. +What is the first of these numbers? +""" + +import time +from typing import Dict, List + + +def timer(func): + def wrapper(*args, **kwargs): + start_time = time.time() + result = func(*args, **kwargs) + end_time = time.time() + print(f"{func.__name__} took {end_time - start_time:.6f} seconds to run.") + return result + + return wrapper + + +def is_probable_prime(n: int) -> bool: + """ + Miller-Rabin素性测试 + 对于n < 3,317,044,064,679,887,385,961,981 (3e24),使用确定性基底集 + """ + if n < 2: + return False + # 小素数快速检查 + small_primes = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37] + for p in small_primes: + if n % p == 0: + return n == p + + # 写成 d * 2^s 的形式 + d = n - 1 + s = 0 + while d % 2 == 0: + d //= 2 + s += 1 + + # 确定性基底集(对64位整数足够) + # 根据研究,测试 [2, 325, 9375, 28178, 450775, 9780504, 1795265022] + # 可以覆盖 < 2^64 的所有数 + test_bases = [2, 325, 9375, 28178, 450775, 9780504, 1795265022] + + for a in test_bases: + if a % n == 0: + continue + x = pow(a, d, n) + if x == 1 or x == n - 1: + continue + for _ in range(s - 1): + x = pow(x, 2, n) + if x == n - 1: + break + else: + return False + return True + + +def factorize(n: int) -> Dict[int, int]: + """ + 优化版试除法因数分解 + 返回: {质因数: 指数, ...} + + 优化策略: + 1. 快速排除小因子(2, 3) + 2. 6k±1优化(跳过所有3的倍数) + 3. 动态更新上界(随着因子被移除,sqrt(n)减小) + 4. 对大余数进行素性预检 + """ + if n < 2: + return {} + + factors = {} + remaining = n + + # 处理负数 + if remaining < 0: + factors[-1] = 1 + remaining = -remaining + + # 步骤1: 快速处理小素数(2和3) + for p in [2, 3]: + if remaining % p == 0: + count = 0 + while remaining % p == 0: + remaining //= p + count += 1 + factors[p] = count + + # 步骤2: 6k±1优化 + # 所有大于3的素数都形如 6k±1 + # 即依次检查 5, 7, 11, 13, 17, 19... + # 步长模式: +2 (到6k+1), +4 (到6k+5, 即下一个6(k+1)-1) + divisor = 5 + step = 2 # 交替使用 2 和 4 + + # 动态计算上界:只需要检查到 sqrt(remaining) + # 随着remaining减小,上界也减小 + while divisor * divisor <= remaining: + if remaining % divisor == 0: + count = 0 + while remaining % divisor == 0: + remaining //= divisor + count += 1 + factors[divisor] = count + # 更新上界(优化关键!) + + divisor += step + step = 6 - step # 2 -> 4 -> 2 -> 4... + + # 步骤3: 如果remaining > 1,说明remaining本身是质数 + # 使用Miller-Rabin确认(对大数避免误判) + if remaining > 1: + # 对于小余数直接确认,大余数用素性测试 + if remaining < 1_000_000 or is_probable_prime(remaining): + factors[remaining] = factors.get(remaining, 0) + 1 + else: + # 极小概率:remaining是合数但试除法未找到因子 + # 此时remaining必为两个大质数的乘积,且都 > sqrt(original_n) + # 对于这种情况,可回退到Pollard's Rho(可选) + factors[remaining] = 1 + + return factors + + +def factorize_list(n: int) -> List[int]: + """返回展开形式的质因数列表,如 12 -> [2, 2, 3]""" + factors = factorize(n) + result = [] + for p, exp in sorted(factors.items()): + result.extend([p] * exp) + return result + + +@timer +def main(limit: int = 4) -> None: + n = 1155 + keep_ok = False + res = [] + while True: + tl = set(factorize_list(n)) + if len(tl) == limit: + res.append(n) + keep_ok = True + if len(res) == limit and keep_ok: + print(res) + break + else: + res = [] + keep_ok = False + n += 1 + + +if __name__ == "__main__": + main() diff --git a/solutions/0047.PrimesFactors/readme.md b/solutions/0047.PrimesFactors/readme.md new file mode 100644 index 0000000..125679e --- /dev/null +++ b/solutions/0047.PrimesFactors/readme.md @@ -0,0 +1,7 @@ +这个问题的核心点是快速质因数分解,我使用试除法来解决的,在4质数4连续的要求下0.3秒获得解。 +在计算5质数5连续的结果时,所需时间就不那么理想了。 + +另外我觉得获得搜索开始的起始点,可能是除了快速计算质因数外,最重要的问题了。 +我只是使用最简单的前n个素数积作为起点,似乎也不是最好的估计。 + +这里我只是提供一个简单的假设,n质数n连续的数字可能需要从n-1和n-2这两组数的平均数位之和,作为搜索的起点.