原题地址 。
— 第 1 天:入口 —
精灵们有好消息,也有坏消息。
好消息是,他们发现了项目管理!这为他们提供了防止往常那种圣诞紧急情况所需的工具。例如,他们现在知道北极装饰需要尽快完成,以便其他关键任务能够按时开始。
坏消息是,他们意识到自己遇到了另一种紧急情况:根据他们的资源规划,他们中没有一个人有时间去装饰北极了!
为了拯救圣诞节,精灵们需要你在 12 月 12 日之前完成北极的装饰工作。
通过解决谜题收集星星。每天将提供两个谜题;当您完成第一个谜题后,第二个谜题将被解锁。每个谜题奖励一颗星星。祝你好运!
你到达了北极基地的入口,准备开始装饰。不幸的是,密码似乎已被更改,所以你无法进入。墙上贴着一份文件,它很有帮助地解释说:
“由于新的安全协议,密码被锁在下面的保险箱里。请查看随附文件以获取新密码组合。”
保险箱有一个刻度盘,上面只有一个箭头;刻度盘周围按顺序排列着数字 0 到 99。当你转动刻度盘时,每到达一个数字就会发出轻微的咔哒声。
随附的文件(你的谜题输入)包含一系列旋转指令,每行一个,这些指令告诉你如何打开保险箱。每条旋转指令以一个 L 或 R 开头,表示旋转方向是向左(朝向较小的数字)还是向右(朝向较大的数字)。然后,旋转指令有一个距离值,表示刻度盘应向该方向旋转多少下。
因此,如果刻度盘指向 11,旋转 R8 将导致刻度盘指向 19。之后,旋转 L19 将导致它指向 0。
因为刻度盘是一个圆,从 0 向左转动一下会使它指向 99。同样地,从 99 向右转动一下会使它指向 0。
因此,如果刻度盘指向 5,旋转 L10 将导致它指向 95。之后,旋转 R5 将导致它指向 0。
刻度盘初始指向 50。
你可以按照指令操作,但你最近参加的官方北极入口安全培训研讨会告诉你,这个保险箱实际上是一个诱饵。真正的密码是在执行序列中任何一次旋转后,刻度盘恰好指向 0 的次数。
例如,假设随附文件包含以下旋转指令:
L68
L30
R48
L5
R60
L55
L1
L99
R14
L82
遵循这些旋转指令将导致刻度盘移动如下:
- 刻度盘初始指向 50。
- 刻度盘旋转
L68指向 82。 - 刻度盘旋转
L30指向 52。 - 刻度盘旋转
R48指向 0。 - 刻度盘旋转
L5指向 95。 - 刻度盘旋转
R60指向 55。 - 刻度盘旋转
L55指向 0。 - 刻度盘旋转
L1指向 99。 - 刻度盘旋转
L99指向 0。 - 刻度盘旋转
R14指向 14。 - 刻度盘旋转
L82指向 32。
因为在这个过程中刻度盘总共指向 0 三次,所以这个例子中的密码是 3。
分析你随附文件中的旋转指令。打开门的实际密码是什么?
此谜题的第一部分已完成!它提供了一颗金星:*
— 第二部分 —
你确信那就是正确的密码,但门没有打开。你敲了敲门,但没有人回应。你一边思考一边堆了一个雪人。
在你为雪人滚雪球的时候,你发现了另一份一定掉进雪里的安全文件:
“由于更新的安全协议,请使用密码方法 0x434C49434B 直到另行通知。”
你从培训研讨会上记得,“方法 0x434C49434B” 意味着你实际上应该计算任何咔哒声导致刻度盘指向 0 的次数,而不管它发生在旋转过程中还是旋转结束时。
按照与上述示例相同的旋转指令,在旋转过程中,刻度盘会有几次额外指向零:
- 刻度盘初始指向 50。
- 刻度盘旋转
L68指向 82;在此次旋转过程中,它指向 0 一次。 - 刻度盘旋转
L30指向 52。 - 刻度盘旋转
R48指向 0。 - 刻度盘旋转
L5指向 95。 - 刻度盘旋转
R60指向 55;在此次旋转过程中,它指向 0 一次。 - 刻度盘旋转
L55指向 0。 - 刻度盘旋转
L1指向 99。 - 刻度盘旋转
L99指向 0。 - 刻度盘旋转
R14指向 14。 - 刻度盘旋转
L82指向 32;在此次旋转过程中,它指向 0 一次。
在这个例子中,刻度盘在旋转结束时指向 0 三次,加上在旋转过程中又指向 0 三次。所以,在这个例子中,新密码将是 6。
请注意:如果刻度盘指向 50,像 R1000 这样的单个旋转将在返回 50 之前,导致刻度盘指向 0 十次!
使用密码方法 0x434C49434B,打开门的密码是什么?
第一部分很简单,我用两种方法做了,第一种递归CTE,很慢,第二种分析函数,很快。
方法1
with recursive t as(select 'L68
L30
R48
L5
R60
L55
L1
L99
R14
L82' t),
b as(select row_number()over()rn,b from(select unnest(string_split(t,chr(10)))b from t))
,a as(select 1 lv,50 v
union all
select lv+1,(v+ case when substr(b,1,1) ='L' then -1 else 1 end * substr(b,2)::int +1000)%100 from a,b where lv <=(select count(*) from b)and rn=lv
)
select count(*) from a where v=0;
这里之所以要加1000, 是因为转的格数最多有3位数,这样保证结果是正数。
因为正式输入数据有4530行,每轮都数一遍b的行数,太笨了。
方法2
with recursive t as(select 'L68
L30
R48
L5
R60
L55
L1
L99
R14
L82' t),
b as(select row_number()over()rn,b from(select unnest(string_split(t,chr(10)))b from t))
,a as(select (sum(case when substr(b,1,1) ='L' then -1 else 1 end * substr(b,2)::int)over(order by rn)+50 +1000)%100 v from b
)
select count(*) from a where v=0;
第二部分,我被一个数学题“开区间(a,b)中间有几个100的倍数”困住了,求助DeepSeek的解答也不对,求助张泽鹏先生,他给了我如下公式:floor((b - 1) / 100) - floor(a / 100),一试果然好用,而把floor改为duckdb整除//运算,结果就不对了。
思路是把每步运算前后指针的实际区间(化为小于100的非负整数前)计算出来,然后用上述公式计算中间经过0点的次数。
with recursive t as(select 'L68
L30
R48
L5
R60
L55
L1
L99
R14
L82' t),
b as(select row_number()over()rn, case when substr(b,1,1) ='L' then -1 else 1 end * substr(b,2)::int b from(select unnest(string_split(t,chr(10)))b from t))
, c as(select rn,b, sum(b)over(order by rn)+50 a, (a+1000)%100 v from b order by rn)
, d as(select c.* , [least(coalesce(lag(v)over(order by rn),50)+b, coalesce(lag(v)over(order by rn),50)), greatest(coalesce(lag(v)over(order by rn),50)+b, coalesce(lag(v)over(order by rn),50))] rg ,
floor((rg[2]- 1) / 100) - floor(rg[1] / 100) zcnt
--(rg[2]- 1) // 100 - (rg[1] // 100) zcnt
from c)
select sum(zcnt)::int +count(case when v=0 then 1 end) from d;

494

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



