利用Duckdb求解Advent of Code 2025第7题 分离器

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

原题地址
— 第 7 天:实验室 —
你感谢了头足类动物的帮助,离开了垃圾压缩机,发现自己身处北极研究区熟悉的走廊中。

根据一个写着"传送器中心"的大标志,他们似乎在研究传送;你忍不住自己尝试了一下,踏上了那个巨大的黄色传送器平台。

突然,你发现自己在一个陌生的房间里!这个房间没有门;唯一的出路就是传送器。不幸的是,传送器似乎在泄漏魔法烟雾。

由于这是一个传送器实验室,周围散落着许多备用零件、手册和诊断设备。连接上一个诊断工具后,它很有帮助地显示了错误代码 0H-N0,这显然意味着其中一个快子歧管出现了问题。

你很快找到了快子歧管的图表(你的谜题输入)。一束快子束从标记为 S 的位置进入歧管;快子束总是向下移动。快子束可以自由通过空的空间(.)。然而,如果快子束遇到一个分离器^),光束会停止;取而代之的是,从分离器的紧邻左侧紧邻右侧各自继续发出一束新的快子束。

例如:

.......S.......
...............
.......^.......
...............
......^.^......
...............
.....^.^.^.....
...............
....^.^...^....
...............
...^.^...^.^...
...............
..^...^.....^..
...............
.^.^.^.^.^...^.
...............

在这个例子中,入射的快子束(|)从 S 向下延伸,直到遇到第一个分离器:

.......S.......
.......|.......
.......^.......
...............
......^.^......
...............
.....^.^.^.....
...............
....^.^...^....
...............
...^.^...^.^...
...............
..^...^.....^..
...............
.^.^.^.^.^...^.
...............

此时,原始光束停止,从分离器发射出两束新光束:

.......S.......
.......|.......
......|^|......
...............
......^.^......
...............
.....^.^.^.....
...............
....^.^...^....
...............
...^.^...^.^...
...............
..^...^.....^..
...............
.^.^.^.^.^...^.
...............

这些光束继续向下,直到遇到更多分离器:

.......S.......
.......|.......
......|^|......
......|.|......
......^.^......
...............
.....^.^.^.....
...............
....^.^...^....
...............
...^.^...^.^...
...............
..^...^.....^..
...............
.^.^.^.^.^...^.
...............

此时,两个分离器总共只产生三束快子束,因为它们都把快子注入到它们之间的同一位置:

.......S.......
.......|.......
......|^|......
......|.|......
.....|^|^|.....
...............
.....^.^.^.....
...............
....^.^...^....
...............
...^.^...^.^...
...............
..^...^.....^..
...............
.^.^.^.^.^...^.
...............

这个过程一直持续,直到所有快子束都到达分离器或离开歧管:

.......S.......
.......|.......
......|^|......
......|.|......
.....|^|^|.....
.....|.|.|.....
....|^|^|^|....
....|.|.|.|....
...|^|^|||^|...
...|.|.|||.|...
..|^|^|||^|^|..
..|.|.|||.|.|..
.|^|||^||.||^|.
.|.|||.||.||.|.
|^|^|^|^|^|||^|
|.|.|.|.|.|||.|

为了修复传送器,你首先需要了解快子歧管的光束分离特性。在这个例子中,快子束总共被分离了 21 次

分析你的歧管图表。光束将被分离多少次?

你的谜题答案是 1658。

此谜题的第一部分已完成!它提供了一颗金星:*

— 第二部分 —
完成对歧管的分析后,你开始修复传送器。然而,当你打开传送器的侧面更换损坏的歧管时,你惊讶地发现这不是一个经典的快子歧管——它是一个量子快子歧管

使用量子快子歧管时,只有单个快子粒子被送入歧管。快子粒子会同时每个遇到的分离器的左路径和右路径。

由于这不可能实现,手册推荐了量子快子分离的多世界诠释:每当一个粒子到达分离器时,实际上时间本身发生了分裂。在一个时间线中,粒子向左走;在另一个时间线中,粒子向右走。

为了修复歧管,你真正需要知道的是一个单粒子完成其所有可能的旅程后,有多少个时间线是活跃的

在上面的例子中,存在许多时间线。例如,有一个粒子总是向左走的时间线:

.......S.......
.......|.......
......|^.......
......|........
.....|^.^......
.....|.........
....|^.^.^.....
....|..........
...|^.^...^....
...|...........
..|^.^...^.^...
..|............
.|^...^.....^..
.|.............
|^.^.^.^.^...^.
|..............

或者,有一个粒子在每个分离器处交替向左和向右走的时间线:

.......S.......
.......|.......
......|^.......
......|........
......^|^......
.......|.......
.....^|^.^.....
......|........
....^.^|..^....
.......|.......
...^.^.|.^.^...
.......|.......
..^...^|....^..
.......|.......
.^.^.^|^.^...^.
......|........

或者,有一个粒子最终到达与交替时间线相同的点,但走了一条完全不同的路径到达那里的时间线:

.......S.......
.......|.......
......|^.......
......|........
.....|^.^......
.....|.........
....|^.^.^.....
....|..........
....^|^...^....
.....|.........
...^.^|..^.^...
......|........
..^..|^.....^..
.....|.........
.^.^.^|^.^...^.
......|........

在这个例子中,粒子最终总共有 40 个不同的时间线。

将量子快子分离的多世界诠释应用到你的歧管图表上。一个单粒子最终会出现在多少个不同的时间线上?

解题思路,
第一问,看起来很像一个异或操作,快子和分离器两个1在同一个列相遇变成0,0和0还是0,后来仔细一看不是异或,因为我要0遇到1还是0,请教DeepSeek后它给出了正确的操作表达式:x & (~y)
SQL如下:

with recursive t as(
select
'.......S.......
...............
.......^.......
...............
......^.^......
...............
.....^.^.^.....
...............
....^.^...^....
...............
...^.^...^.^...
...............
..^...^.....^..
...............
.^.^.^.^.^...^.
...............' t)
, b as(select row_number()over()rn ,translate(b, '.S^', '011')  b from(select unnest(string_split(t, chr(10)))b from t)where instr(b, 'S')>0 or instr(b, '^')>0)
,a as(select 1 lv, b::BITSTRING::int m from b where rn=1
union all
select 1+lv, m & ~( b::BITSTRING::int)| (b::BITSTRING::int <<1) |(b::BITSTRING::int >>1)  from a, b
where lv+1=rn)
, o as(select lv, m::bitstring v,bit_count(v)bc from a)
, p as(select lv, v, bc, lead(v)over(order by lv)&v l ,bit_count(l)bc2  from o)
select sum(bc)-sum(bc2) from p where l is not null;

对于示例数据,它正确得出了分裂次数21, 但是正式输入数据一行有140字符,超过了DuckDB的hugeint范围,所以让DeepSeek把上述SQL改写成Python程序,修改几处BUG后如下所示。
它添加的注释很好地说明了算法,就不多解释了。

# 原 SQL 中的递归 CTE 部分用循环和列表处理来模拟

# 1. 原始数据
data = """.......S.......
...............
.......^.......
...............
......^.^......
...............
.....^.^.^.....
...............
....^.^...^....
...............
...^.^...^.^...
...............
..^...^.....^..
...............
.^.^.^.^.^...^.
..............."""

# 2. 将数据按行分割,并转换为二进制整数
lines = data.split('\n')

# 处理每一行:将 '.'、'S'、'^' 转换为二进制数字
# 规则:'.' -> 0, 'S'/'^' -> 1
binary_rows = []
for line in lines:
    if 'S' in line or '^' in line:
        bin_str = line.replace('.', '0').replace('S', '1').replace('^', '1')
        binary_rows.append(int(bin_str, 2))  # 转换为整数

# 3. 模拟递归计算(实际上是逐行迭代计算)
lv = 1
m = binary_rows[0]  # 第一行
results = [(lv, m, bin(m))]  # 存储每行结果 (层级, 整数, 二进制字符串)

# 处理第 2 行到最后行
for i in range(1, len(binary_rows)):  # 确保不超过数据行数
    lv += 1
    b = binary_rows[i]  # 当前行的二进制整数
    # 核心公式: m & ~b | (b << 1) | (b >> 1)
    m = (m & ~b) | (b << 1) | (b >> 1)
    results.append((lv, m, bin(m)))

# 4. 计算 bc (bit_count) 和 bc2
from math import log2

def bit_count(num):
    """计算整数的二进制表示中 1 的个数"""
    return bin(num).count('1')

# 将结果转换为便于处理的列表
o = [(lv, v, bit_count(v)) for lv, v, _ in results]
# print(o)
# 计算相邻两行之间的重叠比特数
total_bc = 0
total_bc2 = 0

for i in range(len(o) - 1):
    lv, v, bc = o[i]
    next_lv, next_v, next_bc = o[i + 1]
    
    # 当前行与下一行的位与结果
    l = v & next_v
    bc2 = bit_count(l)
    
    total_bc += bc
    total_bc2 += bc2

# 5. 最终计算结果
result = total_bc - total_bc2
print(f"结果: {result}")

将它用于正式输入,通过了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值