🎄Advent of Code 2025 挑战全手写代码 Day 8 - 游乐场
新的一周继续冲刺!今日题目 Playground 难度中等(三星半 ⭐⭐⭐),核心考察:欧氏几何 、最短边排序/Top‑K 、并查集(Union‑Find) 。数据规模为 n≈1000,两两点对约 499,500,Python 基于全量构边 + 并查集可以稳定通过。
📖 题目速览
- 题目地址:adventofcode.com/2025/day/8
- 输入:每行一个 3D 坐标
X,Y,Z,表示接线盒位置。 - 距离:按欧氏距离(建议使用"平方距离 "避免
sqrt)从小到大考虑"盒子对"。 - Part 1:按全局最短的顺序处理 1000 次尝试(包括连接同一连通分量的"无效尝试")。完成后统计所有连通分量大小,取最大的三个相乘。
- Part 2:持续连接 仅在不同分量之间 的最近一对,直到所有点归于一个分量。答案为"最后一次有效合并"两端点的
X轴坐标相乘。

👀 题目大意
- 有很多接线盒悬挂在地下游乐场,输入给出它们的三维坐标。
- 精灵们要用灯串连接接线盒,让电能能在盒子之间传递。连接两盒会把它们所在的电路(连通分量)合并。
- 需要按"距离最近的盒子对"逐步连接:
- Part 1:严格处理 1000 条"全局最近的边",允许无效尝试(同一分量内连接不改变结构,但也要算一次)。最后输出三大分量大小的乘积。
- Part 2:只做有效合并,直到所有盒子成为一个大电路;输出最后一次合并的两个盒子的
X坐标乘积。
🧠 解题思路(Kruskal 视角)
- 边构建:枚举所有点对
(i<j),计算平方距离dist2,形成三元组(dist2, i, j)。 - 稳定排序:存在大量相等距离时,建议用
(dist2, i, j)作为排序键,保证确定性。 - 数据结构:并查集(路径压缩 + 按大小合并),单次近似常数时间。
- Part 1 流程:
- 取全局最短的 1000 条边(可用
heapq.nsmallest或维护大小为 1000 的最大堆 Top‑K)。 - 按距离升序遍历这 1000 条边:如果两端已在同一分量,跳过结构更新但尝试数仍+1;否则执行
union。 - 结束后统计所有节点的分量大小(包含未被触及的单点分量),取前三相乘。
- 取全局最短的 1000 条边(可用
- Part 2 流程:
- 按距离升序扫描全边;仅当
find(i) != find(j)时执行union并记录这条边为"最后有效连接"。 - 当有效合并次数达到
n-1,所有点连通;输出"最后有效连接"的两个端点X的乘积。
- 按距离升序扫描全边;仅当
⏱️ 复杂度分析
- 构边:
O(n^2);排序:O(n^2 log n);并查集:O(n^2 · α(n))(近似常数)。 - 在
n=1000时,Python 能够在合理时间内处理 ~50 万条边。 - 优化建议:距离用"平方距离";仅在需要时使用 Top‑K 堆减少排序成本(Part 1)。
🐞 踩坑与修正
- Part 1 必须"计数尝试",不能只计有效合并;否则答案会偏小。
- 相等距离的边次序不稳定会影响选取的 1000 条边,导致结果波动;用
(dist2, i, j)排序稳定。 - 统计分量大小时应包含所有节点(未参与任何连接的节点也是大小为 1 的分量)。
- 注意读入的文件路径与规模:
K值应为1000。
🧩 关键代码片段(Python 3.12,标准库)
python
import heapq
from pathlib import Path
class UnionFind:
def __init__(self, n: int):
self.parent = list(range(n))
self.size = [1] * n
def find(self, x: int) -> int:
while self.parent[x] != x:
self.parent[x] = self.parent[self.parent[x]]
x = self.parent[x]
return x
def union(self, a: int, b: int) -> bool:
ra, rb = self.find(a), self.find(b)
if ra == rb:
return False
if self.size[ra] < self.size[rb]:
ra, rb = rb, ra
self.parent[rb] = ra
self.size[ra] += self.size[rb]
return True
def squared_distance(p, q):
return sum((p[k] - q[k]) ** 2 for k in range(3))
def solve_part1(points: list[tuple[int, int, int]]) -> int:
n = len(points)
# Build all edges
edges: list[tuple[int, int, int]] = []
for i in range(n - 1):
for j in range(i + 1, n):
edges.append((squared_distance(points[i], points[j]), i, j))
# Take 1000 smallest edges
smallest = heapq.nsmallest(1000, edges)
uf = UnionFind(n)
# Process attempts in ascending order
for _, i, j in sorted(smallest):
uf.union(i, j) # union only if different; attempts always count
# Compute component sizes
comp = {}
for i in range(n):
r = uf.find(i)
comp[r] = comp.get(r, 0) + 1
top3 = sorted(comp.values(), reverse=True)[:3]
return top3[0] * top3[1] * top3[2]
def solve_part2(points: list[tuple[int, int, int]]) -> int:
n = len(points)
edges: list[tuple[int, int, int]] = []
for i in range(n - 1):
for j in range(i + 1, n):
edges.append((squared_distance(points[i], points[j]), i, j))
edges.sort() # stable: (dist2, i, j)
uf = UnionFind(n)
merges = 0
last_i = last_j = 0
for _, i, j in edges:
if uf.union(i, j):
merges += 1
last_i, last_j = i, j
if merges == n - 1:
break
return points[last_i][0] * points[last_j][0]
🧪 验证与表现
- 样例(题面给出)应得到 Part 1:
40;Part 2:25272。 - 真实数据:
n≈1000,构边约 50 万,Python 运行在秒级,注意使用平方距离与稳定排序。
🔗 参考与代码
坚持到最后一题,冲刺就有希望!祝大家 AC 顺利,Happy Coding!🚀 