feat(0047): 添加欧拉47题质因数分解与连续整数检测实现

This commit is contained in:
2026-02-18 18:08:51 +08:00
parent e5868c2649
commit e5064b278d
2 changed files with 175 additions and 0 deletions

View File

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