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

233 lines
6.9 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
def distinct_prime_factors_sieve(limit: int) -> List[int]:
"""
返回一个列表pf其中pf[i]是i的不同质因数个数i从0到limit。
使用筛法计算复杂度O(limit log log limit)。
"""
pf = [0] * (limit + 1)
for p in range(2, limit + 1):
if pf[p] == 0: # p是质数
for multiple in range(p, limit + 1, p):
pf[multiple] += 1
return pf
@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
@timer
def main_key(limit: int = 4) -> None:
if limit < 2:
raise ValueError("limit must be at least 2")
lease = [2, 3]
# 预计算一个足够大的范围这里假设答案不会超过10^7但为了效率动态扩展
# 我们使用一个缓存,按需扩展
_pf_cache = [] # 缓存数组索引i对应数字i的质因数个数
_pf_cache_limit = 0
def ensure_cache(upto: int) -> None:
nonlocal _pf_cache, _pf_cache_limit
if upto <= _pf_cache_limit:
return
# 扩展缓存每次至少扩展到upto或者按一定步长扩展
new_limit = max(upto, _pf_cache_limit * 2 if _pf_cache_limit else upto + 10000)
# 重新计算整个缓存,或者增量更新?为了简单,重新计算整个范围
# 但增量更新较复杂我们重新计算到new_limit
_pf_cache = distinct_prime_factors_sieve(new_limit)
_pf_cache_limit = new_limit
for i in range(2, limit + 1):
if i in [2, 3]:
n = lease[i - 2]
else:
n = lease[i - 3] + lease[i - 4]
start = 10 ** (n - 1)
# 确保缓存至少覆盖 start + 一个估计的窗口大小比如10000
ensure_cache(start + 10000)
keep_ok = False
res = []
current = start
while True:
# 如果当前数字超出缓存,扩展缓存
if current > _pf_cache_limit:
ensure_cache(current + 10000)
if _pf_cache[current] == i:
res.append(current)
keep_ok = True
if len(res) == i and keep_ok:
print(f"{i} - {res}")
if i > 3:
lease.append(len(str(max(res))))
break
else:
res = []
keep_ok = False
current += 1
if __name__ == "__main__":
main()
main_key()