Files
SolutionEuler/solutions/0050.ConsecutivePrimeSum/euler_50_better.py

136 lines
4.5 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 prime 41, can be written as the sum of six consecutive primes:
41 = 2 + 3 + 5 + 7 + 11 + 13
This is the longest sum of consecutive primes that adds to a prime below one-hundred.
The longest sum of consecutive primes below one-thousand that adds to a prime, contains 21 terms,
and is equal to 953.
Which prime, below one-million, can be written as the sum of the most consecutive primes?
"""
import time
from math import isqrt, log
from bitarray import bitarray
def timer(func):
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
elapsed_time = end_time - start_time
print(f"{func.__name__} time: {elapsed_time:.6f} seconds")
return result
return wrapper
def primes_list(limit: int = 10**6) -> list[int]:
if limit < 2:
return []
# 初始化全1假设都是素数0和1置为0
is_prime = bitarray(limit + 1)
is_prime.setall(True)
is_prime[:2] = False # 0和1不是素数
# 只需筛到 sqrt(n)
imax = isqrt(limit) + 1
for i in range(2, imax):
if is_prime[i]:
# 步长i从i*i开始小于i*i的已被更小的素数筛过
is_prime[i * i : limit + 1 : i] = False
# 提取结果
return [i for i, val in enumerate(is_prime) if val]
def get_bound(limit: int = 10**6) -> int:
"""
使用牛顿法估算筛法所需的素数上限。
"""
def f(t: float) -> float:
return t**2 * (2 * log(t) - 1) - 4 * limit
def fp(t: float) -> float:
return 4 * t * log(t)
x, y = limit, limit + 2
while y - x > 1:
y, x = x, x - f(x) / fp(x)
return int(x * (log(x) + log(log(x) - 1)))
def max_primes_sum_best(limit: int = 10**6) -> tuple[int, int] | None:
bound = get_bound(limit)
primes = primes_list(bound)
prime_set = set(primes)
# === 2. 构建前缀和数组 ===
# prefix[i] 表示前 i 个素数的和primes[0] 到 primes[i-1]
prefix = [0]
current_sum = 0
max_possible_length = 0
# 计算从2开始连续累加不超过 limit 的最大素数个数
for p in primes:
current_sum += p
if current_sum >= limit:
break
prefix.append(current_sum)
max_possible_length += 1
# === 3. 搜索最长序列(倒序遍历 + 剪枝)===
best_length = 0
best_prime = 0
# 外层:右端点从大到小遍历(优先尝试更长序列)
for right in range(max_possible_length, 0, -1):
# 关键剪枝1如果右端点 <= 当前最优长度,不可能找到更长序列
# 因为即使从左端点0开始长度也只有 right而 right <= best_length
if right <= best_length:
break
# 关键剪枝2左端点只需考虑到 (right - best_length - 1)
# 因为我们需要的长度是 (right - left),必须满足 > best_length
# 即 left < right - best_length
max_left = right - best_length
for left in range(max_left):
# 计算连续素数之和primes[left] 到 primes[right-1]
consecutive_sum = prefix[right] - prefix[left]
# 修正:如果 sum 已经超过 limitleft 继续增大 sum 会减小,所以不应 break
# 但我们可以加一个判断:如果 prefix[right] - prefix[left] > limit且 left 还在增大...
# 实际上 left 增大sum 减小,所以一旦 sum < limit后续都 < limit
# 简单处理:直接检查,不 break或者可以预先判断但为清晰起见省略
if consecutive_sum >= limit:
continue # 跳过,但继续尝试更大的 leftsum 会变小)
if consecutive_sum in prime_set:
length = right - left
if length > best_length:
best_length = length
best_prime = consecutive_sum
# 更新剪枝边界:后续需要找比当前更长的,所以 max_left 可以缩小
# 但 Python 的 range 已经确定,我们只需依赖外层的 right <= best_length 判断
return best_prime, best_length
@timer
def main():
limit = int(input("limit:"))
max_sum = max_primes_sum_best(limit)
print(f"max primt: {max_sum}")
if __name__ == "__main__":
main()