Advent of Code 2025 day7

day7 的输入是:

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

这是一个超光速粒子歧管的图表(a diagram of the tachyon manifold), S 是发射超光速粒子束的地方。超光速粒子束总是向下移动。超光速粒子束可以自由穿过空白区域 (.)。但是,如果超光速粒子束遇到分束器 (^),光束就会停止;取而代之的是,新的超光速粒子束会被分束,从分束器的正左侧和正右侧继续向下移动。

于是,从 S 发射的超光速离子束最后会被分束成这样:

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

part1 的问题是,超光速离子束被分束了几次?

当光束 (|) 往下碰到分束器 (^) 的时候,就会被分束,所以问题可以变成:

图中存在多少个这样的分束器:

|
^

统计这种结构的分束器的数量,对应的就是超光速离子束被分束的次数。

只要遍历每一行,对其中的每个分束器 (^) 判断它上面是否存在离子束 (|),如果存在,就累计数量。

在遍历时,用一个变量维护上一行的离子束 (|) 状态,就可以方便地进行比较了。


part2 的问题是,实际只有一个超光速粒子,当它碰到分束器的时候,可能往左,也可能往右,统计最终有多少条路径可以走到底部。

例如粒子可以有一下几种走法:

总之路径很多,最终能走到底部都算。

粒子时而往左,时而往右,看起来有非常多的组合。

但仔细一想,对于粒子而言,它只有 3 种选择:

每往下走一次,粒子都要再做一次选择,直到走到底部。

为了更好的观察规律,可以从数据量较小的情况观察,例如只看前 5 行:

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

上面的图中,总共有 4 种路径可以走到底,分别是:

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

如何统计路径呢?可以一步步看看。

为了方便描述,用 diagram 表示这个图,diagram[0] 表示第一行,diagram[0][0] 表示第一行的第一列的位置。

用 timelines[x][y] 来表示在 x 行 y 列时,有多少的路径。

粒子从 S (diagram[0][7]) 出发,只能往下走,走到了 diagram[1][7]。

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

此时 timelines[0][7] = timelines[1][7] 。

然后粒子碰到了一个分束器,可以向左,走到 diagram[2][6] ;也可以向右,走到 diagram[2][8] 。

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

此时 timelines[0][7] = timelines[1][7] = timelines[2][6] + timelines[2][8] 。

也就是说:

粒子继续往下走,式子可以一直写下去:

timelines[0][7] =
timelines[1][7] =
timelines[2][6] + timelines[2][8] =
timelines[3][6] + timelines[3][8] =
timelines[4][5] + timelines[4][7] + timelines[4][7] + timelines[4][9] =
1 + 1 + 1 + 1 =
4

当走到最后一行的时候,粒子已经走到走到底了,就只有一条路径,所以 timelines[4][y] = 1。

可以发现这是把一个大的问题,拆成一个更小的问题,直到这个问题知道确切的答案。可以利用递归实现,递归结束条件就是粒子走到了最后一行。

但是当我用这样的方法实现的时候,我发现代码执行了很久都没有结果。分析一下时间复杂度发现,当粒子到一个分束器,就会一分为二, 假设粒子在每一行都碰到分束器,相当于每往下走一行,计算量就会翻倍。假设有 N 行的话,时间复杂度就是 2 的 N 次方,是一个指数,所以随着 N 增加,时间复杂度会急剧上升。

观察式子,会发现 timelines[4][7] 被重复计算了,可以利用一个哈希表记录被计算过的位置的值,这样,每个位置只会被计算一次,假设有 N 个位置,时间复杂度就是 N,就会快很多了。

具体代码见:day7/solution.py

Webmentions (0)

如果你想回应这篇文章,你可以在你的文章中链接这篇文章,然后在下面输入你的文章的 URL 并提交。你的回应随后会显示在此页面上(如果是垃圾信息我会屏蔽)。如果要更新或删除你的回应,请更新或删除你的文章,然后再次输入该文章的 URL 并提交。(了解有关 Webmention 的更多信息。)


作 者: Spike Leung

创建于: 2025-12-08 Mon 11:22

修改于: 2025-12-08 Mon 11:23

许可证: 署名—非商业性使用—相同方式共享 4.0

支持我: 用你喜欢的方式