利用Python求解Advent of Code 2025第8题 空间接线盒

编程达人挑战赛·第5期 10w+人浏览 351人参与

原题地址

— 第 8 天:游乐场 —
凭借对传送器维护的新理解,你自信地踏上了修复好的传送器平台。

你在一个陌生的传送器平台上重新实体化,发现自己身处一个巨大的地下空间,里面有一个巨大的游乐场!

在游乐场对面,一群精灵正在努力搭建一个雄心勃勃的圣诞装饰项目。通过精心布置,他们悬挂了大量的小型电气接线盒。

他们的计划是用长串的灯来连接接线盒。大多数接线盒不提供电力;然而,当两个接线盒通过一串灯连接时,电力就可以在这两个接线盒之间传递。

精灵们正试图找出哪些接线盒需要连接,以便电力能够到达每一个接线盒。他们甚至有一份所有接线盒在三维空间中位置的列表(你的谜题输入)。

例如:

162,817,812
57,618,57
906,360,560
592,479,940
352,342,300
466,668,158
542,29,236
431,825,988
739,650,466
52,470,668
216,146,977
819,987,18
117,168,530
805,96,715
346,949,466
970,615,88
941,993,340
862,61,35
984,92,344
425,690,689

此列表描述了 20 个接线盒的位置,每行一个。每个位置以 X,Y,Z 坐标给出。因此,列表中的第一个接线盒在 X=162, Y=817, Z=812

为了节省灯串,精灵们希望专注于连接那些根据直线距离尽可能接近的接线盒对。在这个例子中,距离最近的两个接线盒是 162,817,812425,690,689

通过连接这两个接线盒,因为电力可以在它们之间流动,它们成为同一电路的一部分。连接它们之后,有一个包含两个接线盒的电路,剩下的 18 个接线盒则各自保持在自己的独立电路中。

现在,距离最近但尚未直接连接的两个接线盒是 162,817,812431,825,988。连接它们之后,由于 162,817,812 已经连接到另一个接线盒,现在出现了一个包含三个接线盒的电路,以及另外 17 个各包含一个接线盒的电路。

接下来要连接的两个接线盒是 906,360,560805,96,715。连接它们之后,有一个包含 3 个接线盒的电路,一个包含 2 个接线盒的电路,以及 15 个各包含一个接线盒的电路。

接下来的两个接线盒是 431,825,988425,690,689。因为这两个接线盒已经在同一电路中,所以什么也不会发生!

这个过程持续了一段时间,精灵们担心他们没有足够的延长线来连接所有这些电路。他们想知道这些电路会有多大。

在进行完10 次最短连接之后,共有 11 个电路:一个包含 5 个接线盒的电路,一个包含 4 个接线盒的电路,两个各包含 2 个接线盒的电路,以及七个各包含单个接线盒的电路。将三个最大的电路(5、4 和其中一个大小为 2 的电路)的大小相乘,得到 40。

你的列表包含许多接线盒;连接1000 对距离最近的接线盒。之后,将三个最大电路的大小相乘,你会得到什么?

— 第二部分 —
精灵们是对的;他们肯定没有足够的延长线。你需要继续连接接线盒,直到它们全部在一个大电路中。

继续上面的例子,第一次使得所有接线盒形成单个电路的连接是在坐标为 216,146,977117,168,530 的接线盒之间。精灵们需要知道这些接线盒距离墙壁多远,以便选择合适的延长线;将这两个接线盒的 X 坐标相乘(216 和 117)得到 25272

继续连接距离最近的未连接接线盒对,直到它们都在同一个电路中。如果将最后需要连接的两个接线盒的 X 坐标相乘,你会得到什么?

此题涉及有序距离列表的遍历,以及集合的合并,用SQL实现有点困难,所以让DeepSeek帮助实现了,主要卡在英文的理解上,如果有两点个距离最近应该加入,但各自在不同组里了,怎么办?还有这里的10次,1000次,到底是处理了10个点,还是10个最短连接?好在经过试验,确认了以上需求是合并两个组和10个连接。以下代码通过了示例数据和正式输入数据的测试。

import math
from collections import defaultdict

def distance(p1, p2):
    return math.sqrt((p1[0]-p2[0])**2 + (p1[1]-p2[1])**2 + (p1[2]-p2[2])**2)

def main():
    with open('2508-input.txt', 'r') as f:
        lines = f.read().strip().splitlines()

    points = []
    for line in lines:
        x, y, z = map(int, line.strip().split(','))
        points.append((x, y, z))

    n = len(points)
    print(f"总接线盒数量: {n}")
    
    # 生成所有边并排序
    edges = []
    for i in range(n):
        for j in range(i+1, n):
            d = distance(points[i], points[j])
            edges.append((d, i, j))

    edges.sort(key=lambda x: x[0])
    print(f"前5条最短边:")
    for i, (d, a, b) in enumerate(edges[:10]):
        print(f"  {i+1}. 点{a}↔点{b} 距离={d:.2f}")
    
    # 初始化:每个点在自己的电路中
    circuits = []  # 每个电路是一个点集合
    point_to_circuit = {}  # 点到电路的映射
    for i in range(n):
        circuits.append({i})
        point_to_circuit[i] = i
    
    print(f"\n初始状态: 每个点在自己的电路中")
    for i in range(min(5, n)):
        print(f"  电路{i}: 点{i}")
    
    connections_made = 0
    connections_to_show = 10  # 显示前10次连接
    
    for d, a, b in edges:
        if connections_made >= 1000:
            break
            
        connections_made += 1
        
        # 查找点a和点b所在的电路
        circuit_a = point_to_circuit[a]
        circuit_b = point_to_circuit[b]
        
        if connections_made <= connections_to_show:
            print(f"\n{'='*60}")
            print(f"连接 #{connections_made}: 点{a}{points[a]} ↔ 点{b}{points[b]} 距离={d:.2f}")
        
        if circuit_a == circuit_b:
            # 两点已在同一电路,无需操作
            if connections_made <= connections_to_show:
                print(f"  点{a}和点{b}已在同一电路{circuit_a},跳过连接")
            continue
        
        # 获取电路大小
        size_a = len(circuits[circuit_a])
        size_b = len(circuits[circuit_b])
        
        # 情况1:一个点是单点,另一个是多点电路 - 单点加入多点电路
        if size_a == 1 and size_b > 1:
            # 点a是单点,点b在多点电路中,把a加入b的电路
            circuits[circuit_b].add(a)
            point_to_circuit[a] = circuit_b
            circuits[circuit_a] = set()  # 清空单点电路
            
            if connections_made <= connections_to_show:
                print(f"  点{a}(单点)加入电路{circuit_b} (点{b}在其中)")
                print(f"  电路{circuit_b}现在包含: {sorted(circuits[circuit_b])}")
                print(f"  电路{circuit_a}现在为空")
                
        elif size_b == 1 and size_a > 1:
            # 点b是单点,点a在多点电路中,把b加入a的电路
            circuits[circuit_a].add(b)
            point_to_circuit[b] = circuit_a
            circuits[circuit_b] = set()  # 清空单点电路
            
            if connections_made <= connections_to_show:
                print(f"  点{b}(单点)加入电路{circuit_a} (点{a}在其中)")
                print(f"  电路{circuit_a}现在包含: {sorted(circuits[circuit_a])}")
                print(f"  电路{circuit_b}现在为空")
                
        elif size_a == 1 and size_b == 1:
            # 两个都是单点电路,合并为新电路
            # 复用编号较小的电路
            if circuit_a < circuit_b:
                # 把点b从电路b移到电路a
                circuits[circuit_a].add(b)
                point_to_circuit[b] = circuit_a
                circuits[circuit_b] = set()  # 清空电路b
                target_circuit = circuit_a
            else:
                # 把点a从电路a移到电路b
                circuits[circuit_b].add(a)
                point_to_circuit[a] = circuit_b
                circuits[circuit_a] = set()  # 清空电路a
                target_circuit = circuit_b
            
            if connections_made <= connections_to_show:
                print(f"  两个单点电路合并到电路{target_circuit}")
                print(f"  电路{target_circuit}现在包含: 点{a}, 点{b}")
                
        else:
            # 两个点都在多点电路中,合并两个电路
            # 保留编号较小的电路
            if circuit_a < circuit_b:
                target_circuit = circuit_a
                source_circuit = circuit_b
            else:
                target_circuit = circuit_b
                source_circuit = circuit_a
            
            # 把源电路的所有点移到目标电路
            for point in circuits[source_circuit]:
                point_to_circuit[point] = target_circuit
            circuits[target_circuit].update(circuits[source_circuit])
            circuits[source_circuit] = set()  # 清空源电路
            
            if connections_made <= connections_to_show:
                print(f"  两个多点电路合并: 电路{source_circuit}合并到电路{target_circuit}")
                print(f"  电路{target_circuit}现在包含: {sorted(circuits[target_circuit])}")
                print(f"  电路{source_circuit}现在为空")
    
    # 完成1000次连接后的状态
    print(f"\n{'='*60}")
    print(f"完成{connections_made}次有效连接后的状态:")
    
    # 收集非空电路
    non_empty_circuits = []
    for i, circuit in enumerate(circuits):
        if circuit:
            non_empty_circuits.append((i, circuit))
    
    print(f"非空电路数量: {len(non_empty_circuits)}")
    
    # 按电路大小排序
    circuit_sizes = []
    for i, circuit in non_empty_circuits:
        size = len(circuit)
        circuit_sizes.append(size)
        if len(circuit_sizes) <= 10:  # 显示前10个电路
            print(f"  电路{i}: {size}个点 - {sorted(circuit)}")
    
    circuit_sizes.sort(reverse=True)
    
    print(f"\n所有电路大小 (降序): {circuit_sizes}")
    
    if len(circuit_sizes) >= 3:
        result = circuit_sizes[0] * circuit_sizes[1] * circuit_sizes[2]
        print(f"三个最大电路大小: {circuit_sizes[0]}, {circuit_sizes[1]}, {circuit_sizes[2]}")
        print(f"乘积结果: {result}")
    else:
        result = 0
        print("电路数量不足3个")
    
    return result

if __name__ == "__main__":
    main()

事后发现,距离最近两点不在一个电路的情况处理逻辑可统一为,合并两个电路,保留编号较小的,删除编号大的电路,也就是说,跳过获取电路大小以及三种情况的分支,直接else处理, 经检验,结果完全一致。可以减少46行代码。

第二部分很简单,添加一个腾空电路集合次数的指示变量,然后在它等于接线盒总个数减一时,将这当前最短连接的两个接线盒的 X 坐标相乘输出,并退出循环即可。

...
            circuits[source_circuit] = set()  # 清空源电路
            empty_circuits_no+=1
            
            if connections_made <= connections_to_show:
                print(f"  两个多点电路合并: 电路{source_circuit}合并到电路{target_circuit}")
                print(f"  电路{target_circuit}现在包含: {sorted(circuits[target_circuit])}")
                print(f"  电路{source_circuit}现在为空")
            
        if empty_circuits_no>= n-1:
            print(a, b, points[a], points[b])
            break
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值