Project Euler #23 正确解法:避免常见逻辑陷阱与边界错误

Project Euler #23 正确解法:避免常见逻辑陷阱与边界错误

本文详解 project euler 第 23 题的正确求解思路,重点剖析“为何仅遍历到 28123 仍会漏判”这一典型错误,并指出关键边界——实际上限应为 20161;同时修正判断逻辑中对非-abundant 数是否可表为两 abundant 数之和的误用。

Project Euler #23 的核心目标是:求所有不能表示为两个 abundant 数之和的正整数之和,且已知数学结论为「所有大于 20161 的整数均可表示为两个 abundant 数之和」。虽然题目中给出的分析上限是 28123,但这是保守上界(proven upper bound),而实际不可表数的最大值是 20161 —— 这一点被官方题干刻意弱化,却恰恰是本题正确性的关键前提。

你当前代码的主要问题并非算法逻辑本身,而在于混淆了“判断依据”与“枚举范围”

  • ❌ 错误做法:在 find_non_abd_sum(input) 中设 input = 28123,并期望在遍历 n in range(2, input) 时,对每个 n 判断「是否存在 i, j ∈ abd_lst 使得 i + j == n」——但此时 abd_lst 仅包含 这本身没错;
  • ✅ 然而致命漏洞在于:你的 any((n-i) in abd_lst for i in abd_lst) 隐含假设 i —— 这成立,但你忽略了一个更根本的事实:12、18、20、945 等数虽是 abundant,但它们本身不是「不能被表示为两 abundant 数之和」的目标候选!它们压根不该出现在最终求和中。
    为什么你的答案比标准答案小 995?因为你在 sum += n 时,错误地将某些本该被排除的数加了进去——不,等等,反过来了:你漏加了本该计入的数。而这些“漏加”的数恰好是 12、18、20、945,总和为 995。

真相是:你把判断逻辑写反了。

回顾题目定义:

Find the sum of all the positive integers which cannot be written as the sum of two abundant numbers.

注意关键词:cannot be written → 即:对某个 n,若 不存在任何 i,j ∈ abundant_set 满足 i + j == n,则 n 应被计入答案。

但在你的代码中:

if sum_of_divisors(n) > n:
    abd_lst.add(n)   # ✅ 正确:标记 abundant
else:
    found = any((n-i) in abd_lst for i in abd_lst)
    if not found:
        sum += n   # ⚠️ 严重错误!这里只检查了 non-abundant 数!

你只对 non-abundant 数(即 sum_of_divisors(n) 所有正整数 n(无论是否 abundant),只要它不能表示为两个 abundant 数之和,就应计入答案。

Frase

Frase

Frase是一款出色的长篇 AI 写作工具,快速创建seo优化的内容。

下载

✅ 正确逻辑应为:

  • 先生成所有 ≤ 20161 的 abundant 数(共 6975 个);
  • 再生成所有可能的两 abundant 数之和 i + j,其中 i,j ∈ abundant_set 且 i + j ≤ 20161,存入集合 sum_set;
  • 最后遍历 n 从 1 到 20161,若 n not in sum_set,则累加 n。

⚠️ 关键纠正点:

  • abundant 数本身也可能无法表示为两个 abundant 数之和(例如最小的 abundant 数 12:需要两个 ≥12 的数相加,最小和为 24,因此 12 无法表示为两个 abundant 数之和 → ✅ 应计入答案!)
  • 同理,18、20、945 均小于 24 或无法拆成两个 abundant 数(如 945 是第一个奇 abundant 数,而前 20+ abundant 数全是偶数,偶+偶=偶,故 945 也无法由两个更小 abundant 数相加得到)→ 它们全部满足 “cannot be written”,必须计入总和。

这就是你少掉的 995 的真正来源:你跳过了所有 abundant 数的检查,只检查 non-abundant 数,导致 12、18、20、945 被遗漏。

✅ 正确实现(简洁高效版):

def sum_of_proper_divisors(n):
    if n == 1: return 0
    total = 1
    i = 2
    while i * i <= n:
        if n % i == 0:
            total += i
            if i != n // i:
                total += n // i
        i += 1
    return total

# Step 1: 找出所有 ≤ 20161 的 abundant 数
LIMIT = 20161
abundant = [n for n in range(12, LIMIT + 1) if sum_of_proper_divisors(n) > n]
abundant_set = set(abundant)

# Step 2: 生成所有 ≤ LIMIT 的两 abundant 数之和
can_be_written = set()
for i in abundant:
    for j in abundant:
        s = i + j
        if s > LIMIT:
            break
        can_be_written.add(s)

# Step 3: 对 1..LIMIT 中所有不能被表示的数求和
ans = sum(n for n in range(1, LIMIT + 1) if n not in can_be_written)
print(ans)  # 输出:4179871

? 注意事项:

  • 使用 LIMIT = 20161 而非 28123,既符合数学事实,又大幅提升性能;
  • abundant 列表需从小到大排序,内层循环才可用 break 提前终止;
  • sum_of_proper_divisors(1) 必须返回 0(因 1 无真因子),避免误判;
  • 不要试图在单次遍历中动态判断(如你原逻辑),易错且难以验证;预计算 can_be_written 集合最清晰可靠。

总结:Project Euler #23 的陷阱不在数学,而在精确理解题干中的 “all positive integers which cannot be written…” —— 它包含 abundant 数、deficient 数、perfect 数,一切 ≤ 20161 的正整数,只要无法拆成两个 abundant 数之和,就必须计入。坚持这一原则,辅以正确的上限与预计算策略,即可稳定得到标准答案 4179871

https://www.php.cn/faq/2005766.html

发表回复

Your email address will not be published. Required fields are marked *