130. 具有素数全一数性质的合数(Composites with Prime Repunit Property)

完全由数字 1 组成的数称为全一数(repunit)。定义 \(R(k)\) 为长度为 \(k\) 的全一数;例如 \(R(6)=111111\)。 给定正整数 \(n\)\(\gcd(n,10)=1\),可以证明总存在某个 \(k\) 使得 \(R(k)\) 能被 \(n\) 整除,记 \(A(n)\) 为满足该条件的最小 \(k\);例如 \(A(7)=6\)\(A(41)=5\)。 已知对所有素数 \(p>5\)\(p-1\) 都能被 \(A(p)\) 整除。例如当 \(p=41\) 时,\(A(41)=5\),而 \(40\) 能被 \(5\) 整除。 然而,存在少数满足同样性质的合数;前五个例子为 \(91\)\(259\)\(451\)\(481\)\(703\)。 求满足 \(\gcd(n,10)=1\)\(n-1\) 能被 \(A(n)\) 整除的前二十五个合数的和。

分析:本题的关键在于将 \(A(n)\) 的条件转化为模幂判定,从而避免显式计算 \(A(n)\)。核心是 \(A(n) = \operatorname{ord}_{9n}(10)\)(10 模 \(9n\) 的乘法阶),而 \(A(n) \mid (n-1)\) 当且仅当 \(10^{n-1} \equiv 1 \pmod{9n}\)。由此可将搜索简化为逐一检查模幂条件,配合 Miller-Rabin 素数检测筛除素数。

一、数学背景

全一数 \(R(k)\) 可写作:

$$ R(k) = \frac{10^k - 1}{9} $$

\(n\) 整除 \(R(k)\) 等价于 \(9n\) 整除 \(10^k - 1\),即:

$$ R(k) \equiv 0 \pmod{n} \iff 10^k \equiv 1 \pmod{9n} $$

因此 \(A(n)\) 正是 10 在模 \(9n\) 乘法群中的阶:

$$ A(n) = \operatorname{ord}_{9n}(10) $$

条件“\(n-1\) 能被 \(A(n)\) 整除”即 \(A(n) \mid (n-1)\)。由乘法阶的基本性质,\(A(n) \mid (n-1)\) 当且仅当 \(10^{n-1} \equiv 1 \pmod{9n}\)

对于 \(\gcd(n,3)=1\) 的情况,由于 \(10 \equiv 1 \pmod{9}\)\(10^{n-1} \equiv 1 \pmod{9}\) 自动成立,条件退化为 \(10^{n-1} \equiv 1 \pmod{n}\)——这正是基-10 的 Fermat 伪素数判定条件。

二、算法设计

1) 线性搜索框架

\(n=3\) 开始递增遍历奇数(跳过 5 的倍数以保证 \(\gcd(n,10)=1\)),对每个候选 \(n\) 执行两步检查:

2) 搜索终止

累积到目标数量(25 个)时停止。搜索上界设为 \(10^7\),实际搜索过程中远早于此界即找到全部 25 个数。

三、复杂度分析

四、代码实现与说明

"""Project Euler 130 - Composites with Prime Repunit Property."""

from math import gcd
from time import perf_counter


def is_prime_mr(n: int) -> bool:
    """Deterministic Miller-Rabin primality test for n < 2^64."""
    if n < 2:
        return False
    # small primes
    small_primes = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37]
    if n in small_primes:
        return True
    for p in small_primes:
        if n % p == 0:
            return False

    # write n-1 = d * 2^s
    d = n - 1
    s = 0
    while d % 2 == 0:
        d //= 2
        s += 1

    # deterministic bases for n < 2^64
    for a in [2, 3, 5, 7, 11, 13, 17]:
        if a >= n:
            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(target_count: int = 25) -> int:
    """Find the sum of the first `target_count` composite n
    satisfying gcd(n,10)=1 and (n-1) divisible by A(n).
    """
    found = []
    n = 3
    # search bound - increase if needed
    max_n = 10 ** 7

    while len(found) < target_count and n < max_n:
        n += 2  # only odd numbers (gcd with 2)
        if n % 5 == 0:
            continue  # skip multiples of 5
        # Check: 10^(n-1) == 1 (mod 9*n)
        if pow(10, n - 1, 9 * n) == 1:
            if not is_prime_mr(n):
                found.append(n)

    if len(found) < target_count:
        raise RuntimeError(
            f"Only found {len(found)} composites up to {max_n}. "
            "Increase search bound."
        )

    return sum(found)


if __name__ == "__main__":
    t0 = perf_counter()
    result = solve(25)
    elapsed = perf_counter() - t0
    print(f"Result: {result}")
    print(f"Time: {elapsed:.3f}s")

导入与常量

is_prime_mr

solve

主入口