128. 六边形地砖差值(Hexagonal Tile Differences)

一个六边形地砖上写有数字 \(1\),它被一圈六块六边形地砖所环绕。从"12点钟方向"开始,按逆时针方向为地砖编号 \(2\)\(7\)

以同样的方式添加新的圈层,接下来的圈层编号依次为 \(8\)\(19\)\(20\)\(37\)\(38\)\(61\),以此类推。

通过计算地砖 \(n\) 与其六个邻居之间的差值,我们定义 \(\operatorname{PD}(n)\) 为这些差值中素数的个数。

例如,顺时针绕地砖 \(8\) 走一圈,差值分别为 \(12, 29, 11, 6, 1\)\(13\)。因此 \(\operatorname{PD}(8) = 3\)

类似地,绕地砖 \(17\) 的差值为 \(1, 17, 16, 1, 11\)\(10\),因此 \(\operatorname{PD}(17) = 2\)

可以证明 \(\operatorname{PD}(n)\) 的最大值为 \(3\)

将所有满足 \(\operatorname{PD}(n) = 3\) 的地砖按升序排列构成一个序列,第 \(10\) 块地砖是 \(271\)

找出该序列中第 \(2000\) 块地砖。

一、数学背景

六边形网格的坐标表示

采用立方体坐标 \((x, y, z)\) 满足 \(x+y+z=0\)。中心地砖 \(1\) 位于 \((0,0,0)\)。六个邻居方向向量为:

$$ \begin{aligned} \mathbf{v}_0 &= (1,-1,0), \quad \mathbf{v}_1 = (1,0,-1), \quad \mathbf{v}_2 = (0,1,-1) \\ \mathbf{v}_3 &= (-1,1,0), \quad \mathbf{v}_4 = (-1,0,1), \quad \mathbf{v}_5 = (0,-1,1) \end{aligned} $$

地砖 \((x,y,z)\) 所在的圈层 \(L\) 定义为 \(L = \max(|x|,|y|,|z|)\)。圈层 \(k\)\(k \ge 1\))包含 \(6k\) 块地砖,编号从"12点钟方向"开始逆时针排列。

圈层 \(k\) 的起始地砖(12点钟位置)编号为:

$$s_k = 3k(k-1) + 2 \quad (k \ge 1)$$

圈层 \(k\) 的终止地砖(恰好位于12点钟顺时针方向)编号为:

$$e_k = 3k(k+1) + 1 \quad (k \ge 1)$$

哪些地砖可能满足 \(\operatorname{PD}(n) = 3\)

通过穷举前若干圈层的所有地砖,可以发现:只有位于六边形环的"角"位置的地砖才可能具有 \(\operatorname{PD}=3\)。具体而言,仅有 \(s_k\)(12点钟方向的角)和 \(e_k\)(相邻的角,位于约1点钟方向)可能满足条件,外加中心地砖 \(1\)

\(s_k\) 的邻居差分析

地砖 \(s_k\) 坐标为 \((0, k, -k)\)。六个邻居及其差值:

方向 邻居坐标 邻居编号 差值
\(\mathbf{v}_0\) \((1, k-1, -k)\) \(s_k+1\) \(1\)
\(\mathbf{v}_1\) \((1, k, -k-1)\) \(s_{k+1}+1\) \(6k+1\)
\(\mathbf{v}_2\) \((0, k+1, -k-1)\) \(s_{k+1}\) \(6k\)
\(\mathbf{v}_3\) \((-1, k+1, -k)\) \(e_{k+1}\) \(12k+5\)
\(\mathbf{v}_4\) \((-1, k, -k+1)\) \(e_k\) \(6k-1\)
\(\mathbf{v}_5\) \((0, k-1, -k+1)\) \(s_{k-1}\) \(6(k-1)\)

其中 \(1\) 不是素数;\(6k\)\(6(k-1)\) 为偶数(\(k \ge 1\)\(>2\)),不是素数。因此只有三个候选值可能为素数:

$$\operatorname{PD}(s_k) = 3 \iff 6k-1,\ 6k+1,\ 12k+5 \text{ 均为素数}$$

\(e_k\) 的邻居差分析

地砖 \(e_k\) 坐标为 \((-1, k, -k+1)\)。类似分析可得六个邻居差值:

方向 差值
\(\mathbf{v}_4\) \(1\)
\(\mathbf{v}_1\) \(6k-1\)
\(\mathbf{v}_0\) \(12k-7\)
\(\mathbf{v}_5\) \(6k\)
\(\mathbf{v}_2\) \(6(k+1)\)
\(\mathbf{v}_3\) \(6k+5\)

其中 \(1\)\(6k\)\(6(k+1)\) 均不是素数。因此:

$$\operatorname{PD}(e_k) = 3 \iff 6k-1,\ 12k-7,\ 6k+5 \text{ 均为素数}$$

(注意:\(k=1\)\(e_1=7\) 的邻居情况有差异,因内层邻居是中心地砖 \(1\) 而非 \(s_0\)。实际计算 \(e_1\)\(\operatorname{PD}=2\),不满足条件。)

二、算法设计

基于上述分析,我们只需遍历 \(k = 1, 2, 3, \dots\),对每个 \(k\) 检查:

  1. \(6k-1\)\(6k+1\)\(12k+5\) 均为素数,则将 \(s_k = 3k(k-1)+2\) 加入结果列表。
  2. \(k \ge 2\)\(6k-1\)\(12k-7\)\(6k+5\) 均为素数,则将 \(e_k = 3k(k+1)+1\) 加入结果列表。

由于 \(s_k\)\(e_k\) 都要求 \(6k-1\) 为素数,可将其作为前置条件,避免冗余的素性检测。

初始结果列表包含地砖 \(1\)\(\operatorname{PD}(1)=3\) 恒成立)。

三、复杂度分析

总体运算量约为 \(69563 \times 4 \times 168 \approx 4.7 \times 10^7\) 次除法运算,在 Python 中可在 \(0.5\) 秒内完成。

四、代码实现与说明

核心素性检测

def is_prime(n):
    """使用试除法检测素数,预计算小素数表"""
    if n < 2:
        return False
    limit = int(n ** 0.5)
    for p in SMALL_PRIMES:
        if p > limit:
            return True
        if n % p == 0:
            return False
    p = SMALL_PRIMES[-1] + 2
    while p <= limit:
        if n % p == 0:
            return False
        p += 2
    return True

主循环

pd3_tiles = [1]  # 地砖1恒有PD(3)
k = 1
while len(pd3_tiles) < 2000:
    a = 6 * k - 1
    if is_prime(a):
        # 检查s_k条件
        if is_prime(6 * k + 1) and is_prime(12 * k + 5):
            pd3_tiles.append(3 * k * (k - 1) + 2)
        # 检查e_k条件 (k >= 2)
        if k >= 2 and is_prime(12 * k - 7) and is_prime(6 * k + 5):
            pd3_tiles.append(3 * k * (k + 1) + 1)
    k += 1