Files
SolutionEuler/solutions/0047.PrimesFactors/euler_47.py

169 lines
4.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
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()