146. 质数模式研究(Investigating a Prime Pattern)

使得 \(n^2+1\)\(n^2+3\)\(n^2+7\)\(n^2+9\)\(n^2+13\)\(n^2+27\) 为连续质数的最小正整数 \(n\)\(10\)。一百万以下所有这样的整数 \(n\) 之和为 \(1242490\)

求一亿五千万以下所有这样的整数 \(n\) 之和。

分析:\(n\) 必须为偶数(否则 \(n^2+1\)\(>2\) 的偶数),且由模 \(3,5,7,\dots\) 等小质数约束,大量 \(n\) 被筛除。用模乘积 \(P=9699690\)\(2\cdot3\cdot5\cdot7\cdot11\cdot13\cdot17\cdot19\))预计算有效剩余类,仅约 24024 个。对每个候选 \(n\),用 Miller-Rabin 检验 6 个目标数与 2 个中间数。

一、数学背景

要使 \(n^2+1, n^2+3, n^2+7, n^2+9, n^2+13, n^2+27\)连续质数,需要: 1. 这 6 个数都是质数。 2. 它们之间的所有奇数(\(n^2+5,11,15,17,19,21,23,25\))都是合数。

\(n\) 的模约束

更进一步,对质数 \(p=7,11,13,17,19,\dots\) 筛选 \(n\bmod p\) 的合法取值,使 \(n^2+k\) 对目标偏移量 \(k\in\{1,3,7,9,13,27\}\) 均不被 \(p\) 整除。

中间数的自动合数性

\(n\equiv\pm1\pmod3\)\(n\equiv0\pmod5\) 的条件下: - \(n^2+5, n^2+11, n^2+17, n^2+23\) 均被 \(3\) 整除(\(n^2\equiv1\pmod3\))。 - \(n^2+15, n^2+25\) 均被 \(5\) 整除(\(n^2\equiv0\pmod5\))。 - 仅 \(n^2+19\)\(n^2+21\) 不自动为合数,需显式检测。

二、算法设计

筛法生成有效剩余类

选取小质数集合 \(S=\{2,3,5,7,11,13,17,19\}\),乘积 \(P=9699690\)。用 bytearray 构建长度为 \(P\) 的布尔筛,初始全为真。对每个 \(p\in S\): - 枚举 \(r=0,\dots,p-1\),若 \(r^2+k\equiv0\pmod p\) 对任意目标 \(k\) 成立,则 \(r\) 为禁止剩余类。 - 在筛中将所有满足 \(n\equiv r\pmod p\) 的位置标记为假。

最终筛中为真的位置即为有效剩余类。

候选迭代与质数检验

对每个有效剩余类 \(r\),以 \(P\) 为步长枚举 \(n=r, r+P, r+2P, \dots < 1.5\times10^8\)。对每个候选 \(n\): - 用 Miller-Rabin 检测 6 个目标数是否均为质数。若任一不是,跳过。 - 用 Miller-Rabin 检测 \(n^2+19\)\(n^2+21\) 是否为合数。若任一是质数,跳过。 - 通过全部检验则累加 \(n\)

Miller-Rabin 使用确定性 7 基组 \(\{2,325,9375,28178,450775,9780504,1795265022\}\),覆盖全部 64 位整数。

三、复杂度分析

四、代码实现与说明

"""
Project Euler Problem 146: Investigating a Prime Pattern

Find the sum of all integers n < 150,000,000 such that:
n^2+1, n^2+3, n^2+7, n^2+9, n^2+13, n^2+27 are consecutive primes.
"""

from math import isqrt

TARGET_OFFSETS = (1, 3, 7, 9, 13, 27)
EXTRA_CHECK_OFFSETS = (19, 21)

FILTER_PRIMES = (2, 3, 5, 7, 11, 13, 17, 19)

MR_BASES = (2, 325, 9375, 28178, 450775, 9780504, 1795265022)


def is_prime_mr(n: int) -> bool:
    """Miller-Rabin deterministic primality test for 64-bit integers."""
    if n < 2:
        return False
    if n in (2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37):
        return True
    if n % 2 == 0:
        return False

    d = n - 1
    s = 0
    while d % 2 == 0:
        d //= 2
        s += 1

    for a in MR_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 = (x * x) % n
            if x == n - 1:
                break
        else:
            return False
    return True


def solve() -> int:
    N = 150_000_000

    P = 1
    for p in FILTER_PRIMES:
        P *= p

    sieve = bytearray(b'\x01') * P
    for p in FILTER_PRIMES:
        forbidden = bytearray(p)
        for r in range(p):
            r2 = (r * r) % p
            for off in TARGET_OFFSETS:
                if (r2 + off) % p == 0:
                    forbidden[r] = 1
                    break
        for f in range(p):
            if forbidden[f]:
                sieve[f:P:p] = b'\x00' * len(sieve[f:P:p])

    total = 0
    for r in range(P):
        if not sieve[r]:
            continue
        n = r
        while n < N:
            n2 = n * n

            all_prime = True
            for off in TARGET_OFFSETS:
                if not is_prime_mr(n2 + off):
                    all_prime = False
                    break
            if not all_prime:
                n += P
                continue

            bad = False
            for off in EXTRA_CHECK_OFFSETS:
                if is_prime_mr(n2 + off):
                    bad = True
                    break
            if bad:
                n += P
                continue

            total += n
            n += P

    return total


if __name__ == "__main__":
    result = solve()
    print(result)

代码说明:

常量区

is_prime_mr

solve

筛法

候选迭代

主程序