原题地址 。
— 第8天:闹鬼荒原 —
你正骑着骆驼穿越沙漠岛,突然发现一场沙暴正在迅速逼近。当你转身想要提醒精灵时,她竟在你眼前消失了!不过公平地说,就在几分钟前,她还刚刚警告过你要小心鬼魂。
骆驼的一个袋子上标着“地图”——果然,里面装满了关于如何在沙漠中导航的文件(你的谜题输入)。至少,你相当确定这些就是地图;其中一份文件包含了一系列左/右指令,其余的文件似乎描述了一种带标签节点的网络结构。
看来你需要利用这些左/右指令在网络中导航。也许如果你让骆驼按照同样的指令前进,就能逃离这片闹鬼荒原!
在仔细查看地图后,有两个节点显得格外突出:AAA 和 ZZZ。你觉得 AAA 就是你当前所在的位置,而你需要跟随左/右指令前进,直到抵达 ZZZ。
文件的格式为每个节点单独定义。例如:
RL
AAA = (BBB, CCC)
BBB = (DDD, EEE)
CCC = (ZZZ, GGG)
DDD = (DDD, DDD)
EEE = (EEE, EEE)
GGG = (GGG, GGG)
ZZZ = (ZZZ, ZZZ)
从 AAA 开始,你需要根据输入中的下一个左/右指令查找下一个元素。在这个例子中,从 AAA 开始,先向右(R),选择 AAA 的右侧元素 CCC。然后,L 表示选择 CCC 的左侧元素 ZZZ。通过跟随左/右指令,你在 2 步内到达了 ZZZ。
当然,你可能无法立即找到 ZZZ。如果左/右指令用完了,就重复整个指令序列:RL 实际上意味着 RLRLRLRLRLRLRLRL… 以此类推。例如,下面这种情况需要 6 步才能到达 ZZZ:
LLR
AAA = (BBB, BBB)
BBB = (AAA, ZZZ)
ZZZ = (ZZZ, ZZZ)
从 AAA 开始,跟随左/右指令。到达 ZZZ 需要多少步?
— 第二部分 —
沙暴已经到来,而你却丝毫没有更接近逃离这片荒原。你让骆驼按照指令前进,但几乎还没有离开起始位置。要逃脱看来需要非常多的步数!
如果这幅地图不是给人用的呢——如果它是给鬼魂用的呢?鬼魂难道会受时空法则的束缚吗?只有一个办法能知道答案。
在更仔细地查看地图后,一个奇怪的现象引起了你的注意:名称以 A 结尾的节点数量与以 Z 结尾的节点数量相等!如果你是鬼魂,你可能会从所有以 A 结尾的节点同时出发,并沿着所有路径同步前进,直到它们同时到达以 Z 结尾的节点。
例如:
LR
11A = (11B, XXX)
11B = (XXX, 11Z)
11Z = (11B, XXX)
22A = (22B, XXX)
22B = (22C, 22C)
22C = (22Z, 22Z)
22Z = (22B, 22B)
XXX = (XXX, XXX)
这里有两个起始节点:11A 和 22A(因为它们都以 A 结尾)。当你遵循每一条左/右指令时,同时用它来从你当前所在的这两个节点导航出去。重复这个过程,直到你当前所在的所有节点都以 Z 结尾。(如果只有部分节点以 Z 结尾,它们的行为就和任何其他节点一样,你照常继续前进。)在这个例子中,你会按以下步骤进行:
步骤 0:你位于 11A 和 22A。
步骤 1:你选择所有左路径,到达 11B 和 22B。
步骤 2:你选择所有右路径,到达 11Z 和 22C。
步骤 3:你选择所有左路径,到达 11B 和 22Z。
步骤 4:你选择所有右路径,到达 11Z 和 22B。
步骤 5:你选择所有左路径,到达 11B 和 22C。
步骤 6:你选择所有右路径,到达 11Z 和 22Z。
所以,在这个例子中,经过 6 步后,你最终完全位于以 Z 结尾的节点上。
从所有以 A 结尾的节点同时出发。需要多少步才能让你只位于以 Z 结尾的节点上?
第一部分就是从AAA的行出发,按指令走,递归子查询找到上一个数据行和指令指向的下一行,在指令字符串中循环读取下一个指令,直到到达ZZZ即可。
with recursive t as(select 'RL
AAA = (BBB, CCC)
BBB = (DDD, EEE)
CCC = (ZZZ, GGG)
DDD = (DDD, DDD)
EEE = (EEE, EEE)
GGG = (GGG, GGG)
ZZZ = (ZZZ, ZZZ)' t),
i as(select substr(t,1,instr(t,chr(10))-1)cms from t)
--from i;
,
b as(select row_number()over()rn,substr(b,1,3)v,substr(b,8,3)l,substr(b,13,3)r from(select unnest(string_split(t,chr(10)))b from t) where instr(b,'=')>0)
--from b;
,a as(select 1 lv,v,l,r from b where v='AAA'
union all
select lv+1, b.v,b.l,b.r
from b,a,i
where b.v=case substr(cms,(lv-1)%length(cms)+1,1) when 'L' then a.l when 'R' then a.r end and
a.v<>'ZZZ'
)
select count(*)-1 from a;
第二部分的关键是,所有行的v都是以Z结尾才能停止执行指令。
with recursive t as(select 'LR
11A = (11B, XXX)
11B = (XXX, 11Z)
11Z = (11B, XXX)
22A = (22B, XXX)
22B = (22C, 22C)
22C = (22Z, 22Z)
22Z = (22B, 22B)
XXX = (XXX, XXX)' t),
i as(select substr(t,1,instr(t,chr(10))-1)cms from t)
--from i;
,
b as(select row_number()over()rn,substr(b,1,3)v,substr(b,8,3)l,substr(b,13,3)r from(select unnest(string_split(t,chr(10)))b from t) where instr(b,'=')>0)
--from b;
,a as(select 1 lv,v,l,r from b where substr(v,3,1)='A'
union all
select lv+1, b.v,b.l,b.r
from b,a,i
where b.v=case substr(cms,(lv-1)%length(cms)+1,1) when 'L' then a.l when 'R' then a.r end and
exists(select 1 from a where substr(a.v,3,1)<>'Z')
)
--from a;
select max(lv)-1 from a;
以上代码数据量少时能用,但对于正式数据,对照表有730行,其中A结尾的有6行。上述程序就不可行了,改用以下程序找出每个Z结尾的最小迭代次数,然后求它们的最小公倍数
with recursive t as(select 'LR
11A = (11B, XXX)
11B = (XXX, 11Z)
11Z = (11B, XXX)
22A = (22B, XXX)
22B = (22C, 22C)
22C = (22Z, 22Z)
22Z = (22B, 22B)
XXX = (XXX, XXX)' t),
i as(select substr(t,1,instr(t,chr(10))-1)cms from t)
,
b as(select row_number()over()rn,substr(b,1,3)v,substr(b,8,3)l,substr(b,13,3)r from(select unnest(string_split(t,chr(10)))b from t) where instr(b,'=')>0)
,a as(select 1 lv,v,l,r from b where substr(v,3,1)='A'
union all
select lv+1, b.v,b.l,b.r
from b,a,i
where b.v=case substr(cms,(lv-1)%length(cms)+1,1) when 'L' then a.l when 'R' then a.r end and
substr(a.v,3,1)<>'Z'
)
r as(select row_number()over(order by lv)rn,lv-1 n from a where substr(a.v,3,1)='Z')
,lc as(select 1 lv,1::bigint v
union all
select lv+1,lcm(lc.v,n) from r,lc where rn=lv and lv<=(select count(*) from r)
)
select max(v) from lc;
最小公倍数可以通过lcm函数求得。
以上方法可行是由数据的特点决定的。
from a where substr(a.v,3,1)='Z';
┌───────┬─────────┬─────────┬─────────┐
│ lv │ v │ l │ r │
│ int32 │ varchar │ varchar │ varchar │
├───────┼─────────┼─────────┼─────────┤
│ 11568 │ GJZ │ BFC │ QQJ │
│ 12644 │ PSZ │ FSF │ QSM │
│ 15872 │ ZZZ │ DHJ │ RMT │
│ 16410 │ TKZ │ NND │ QKS │
│ 19638 │ HGZ │ QRS │ SMR │
│ 21252 │ RFZ │ XLR │ QKM │
└───────┴─────────┴─────────┴─────────┘
from b where substr(b.v,3,1)='A';
┌───────┬─────────┬─────────┬─────────┐
│ rn │ v │ l │ r │
│ int64 │ varchar │ varchar │ varchar │
├───────┼─────────┼─────────┼─────────┤
│ 312 │ XSA │ QKS │ NND │
│ 339 │ VVA │ QSM │ FSF │
│ 359 │ TTA │ QKM │ XLR │
│ 607 │ AAA │ RMT │ DHJ │
│ 634 │ NBA │ SMR │ QRS │
│ 709 │ MHA │ QQJ │ BFC │
└───────┴─────────┴─────────┴─────────┘
以A结尾的行的左右值刚好和以Z结尾的行的右值和左值重合,而指令字符串的长度恰好是各个赛道迭代次数的最大公约数,开头的指令是L、结束的指令是R,形成了闭环。
比如一开始XSA向左得到QKS,现在TKZ向右也得到QKS。
select length(cms) from i;
┌─────────────┐
│ length(cms) │
│ int64 │
├─────────────┤
│ 269 │
└─────────────┘
print(math.gcd(11567,12643,15871,16409,19637,21251))
269
所以每个得到Z结尾的指令都在重复执行相同的指令,等指令总长度达到最小公倍数时,几个赛道同时得到Z结尾。
DuckDB解AoC2023第8题

706

被折叠的 条评论
为什么被折叠?



