
本指南展示了一些可能的解决方案,用于解决初期关卡的复杂度和速度问题(不一定同时兼顾),并对所使用的算法提供了一些解释。 1-1 开关组装 复杂度与速度(复杂度 120 / 120,速度 4 / 4)

这里没有难度,只需跟随教程即可。 1-2 威斯敏斯特钟声 复杂度与速度(复杂度 120/120,速度 10/10)

这里没有难度,只需跟随教程即可。 1-3 音调选择器 复杂度和速度(复杂度 180/230,速度 10/10)

一个节点具有较高的复杂度(30点),因此如果你正尝试完成【复杂度挑战】,请避免使用过多节点。在这个谜题中只有4种不同的音调,所以你只需要4个节点。 无论过渡的复杂程度如何,无论是“默认”过渡还是带有多个AND条件的测试,每次过渡都花费10点。因此,如果你想降低复杂度,可以大量使用过渡。 对于这个谜题,主要在于为节点之间的过渡设置正确的条件。 1-4 控制阀 复杂度和速度(复杂度 190/190,速度 4/4)

这里没有难度,只需跟随教程即可。 1-5 记录器 复杂度与速度(复杂度 150/150,速度 4/4)

只有两种可能的操作:写入X或写入-。这意味着有2个节点。然后你可以添加条件,甚至可以添加多个连接相同节点的条件(这样就创建了一个或条件)。 1-6 欢迎钟声 复杂度与速度(复杂度160/160,速度4/4)

和之前类似的谜题:只有两个动作可以执行:按铃C或按铃E。 它们需要按不同顺序使用这一情况可以通过条件来处理,这会降低复杂度评分。 哦,还有,如果没有条件匹配,就什么都不会发生,测试用例也会结束。所以“否则,不执行任何操作”的要求实际上通过……不执行任何操作来自动满足。 1-7 自动传输 复杂度和速度(复杂度160/160,速度4/4)

与1-4相同:三种可能状态,因此对应三个节点。其余为达成这些状态的条件。 1-8 心跳压缩器 复杂度与速度(复杂度 130/130,速度 5/5)

这里最复杂的情况是执行3次心跳(其他情况需要的心跳次数更少)。在游戏的这个阶段,你可用的工具无法进行任何迭代/循环操作。因此,如果你尝试仅使用单个节点,将无法记录已经完成的心跳次数,也就无法在正确的时间停止。所以唯一的选择是在3个节点中设置3次心跳,并设定正确的条件。 2-1 电缆释放

同时满足复杂度和速度要求的简单解决方案。数组始终从位置1开始(从右侧算起)。要到达第3个位置,我们需要向左移动两次,然后将其开启。 2-2 重置器 关于复杂度,你必须尝试在同一个节点中设置多个动作。你不能在同一设备上设置多个动作,但可以在不同设备上执行动作。在这种情况下,你可以在同一时间增加计数器并移动到下一个位置(如图中的节点Q3)。

关于速度,你需要在测试执行过程中尽量减少步骤。这可以通过以下方式实现: 1. 避免使用积分器:数组中只有4个位置,且节点数量对速度并不重要,因此避免花费时间/步骤来增加积分器并测试其值。 2. 避免将已为0的值设为0。因此,应先使用条件判断这样做是否有用。

2-3 组头 让我们从复杂度的解决方案开始。

红色高亮部分只是添加当前咖啡所需的4种连续原料,并进行了一些优化(例如卡布奇诺和拿铁只有1种原料不同,因此除了该原料外,路径基本相同。不过你在前面的章节中应该已经了解到这一点了)。 左侧部分是每款新咖啡的起点,准备将任务分配给处理原料的序列。 右上角部分是供应咖啡并进入下一杯(记住,如果在不同设备上操作,你可以在单个节点中设置2个动作)。 那么速度方面呢? 但你可能已经注意到左侧节点是空的。所以让我们进一步优化,以避免这个不必要的步骤。如果你让节点X连接到那个空白节点,然后再进一步连接到节点Y,那么你可以用一个等效条件直接从X连接到Y来替代。移除该节点后,我们就能得到同时满足复杂度和速度的最终解决方案(我已像之前一样高亮显示了相同的红色部分,以帮助可视化)。

2-4 计时器 关于复杂度,你可以使用新的映射器(设置变量以确定要写入I2的X数量),然后通过递减I2来执行循环。

为了提升速度,我们需要节省一些操作。这可以通过改进【默认规则】以及在不必要时避免准备映射器来实现。

我想借此机会用一个不那么复杂的谜题来展示一种不同的速度解决方案。也许不是最优雅的,但却是最快的。这被称为循环展开,其原理是去除所有用于管理循环的操作,即使这意味着要执行重复的任务。在这个案例中,并非绝对必要的操作包括: 使用映射器(我们可以改用RTMR) 执行最后的I2减1操作(无需将其从1减到0,因为这已经是最后一个了) 更一般地说,如果我们移除映射器,那么计数器和I2也可以一并移除。 因此,我们可以通过逐个写入X来“展开”循环。虽然繁琐,但确实有效……而且你能获得比实际要求更高的速度。

2-5 泵 此装置可同时提升复杂度和速度。

为此,我们主要使用两个节点: 创建一个映射器,设置正确的写入值,即9减X的计算结果(0变为9,1变为8,2变为7,依此类推) 一个用于移至下一个数字的节点 和往常一样,不要忘记通过在转换上使用条件来不断测试是否已到达终点,以避免不必要的步骤。 2-6 加热器 这同时适用于复杂度和速度。

思路如下: 在I2中准备一个映射计数器,用于记录将要写入的“2”的数量。映射规则为:0和1映射为0,2和3映射为1,4和5映射为2,以此类推。 然后添加一个节点用于循环写入这些“2”(通过自身的转换来迭代,并使用递减计数器来判断何时停止循环)。 最后添加一个节点,若初始数字为偶数则写入“1”。 不要忘记在每个转换上使用正确的条件,确保只有在必要时才进入下一步,否则将无法达到速度要求。 2-7 主变速箱 思路是从右到左遍历数组中的不同位置,每当找到“1”时就增加计数器。为此,我们需要添加: 一个积分器Icnt,用于统计已找到的“1”的数量 一个积分器Ipos,用于确定当前在数组中的位置 一个映射器,用于将结果从Icnt复制到预期的Rspwr寄存器 最后,将响应值从Icnt复制到预期的Rspwr。 复杂度(复杂度250/260,速度16/14)

一个名为【Q2】的节点用于移动到下一个位置,具体方式是向左移动并增加Ipos变量。 一个名为【Qinc counter】的节点用于在检测到“1”时增加Iinc计数器。 当我们在Q2节点处于数组的特定位置时,有3种选择: - 已到达数组末尾(Ipos的值已达到4),此时通过映射器进入最后步骤。 - 未到达末尾且当前数组值为“1”——在返回Q2移动到下一个位置之前,先增加计数器。 - 未到达末尾且当前数组值为“0”——直接再次进入Q2以移动到下一个位置。 速度(复杂度270/260,速度12/14)

在这种情况下,我们希望尽量减少转换的数量,尤其是在需要增加Icnt(指令计数)的时候。不必为增加计数器设置特定节点(比如你的程序需要先检测计数器是否为“1”,然后跳转到增加计数器的节点,再返回Q2),我们可以这样做: 一个节点用于处理当前位置值为0的情况(图中下方)。它会移动到下一个位置(向左移动+增加Ipos(指令位置))。 一个节点用于处理当前位置值为1的情况(图中上方)。它会增加Ipos并移动到下一个位置(向左移动+增加Ipos)。接下来主要就是在每个转换上设置正确的条件,以在顶部(数组=1)、底部(数组=0)或到达终点时切换到最终映射器之间进行交替。2-8 硬币机制 复杂度 (复杂度 260/310,速度 33.33/20.08)

工作原理如下: 我们创建一个积分器Itot来记录到目前为止已处理的便士数量。 我们创建一个积分器Icoin来记录当前硬币的便士价值。 工作流程如下: 对于当前硬币,我们使用映射器将当前硬币的价值设置到Icoin中。映射器的映射规则为:0对应0,1对应1,3对应3,6对应6。 然后,节点Q2将Icoin中的金额累加到Itot上。由于我们只能进行递增或递减操作,无法直接相加数值,因此需要通过循环来实现:每次将Icoin减1,同时将Itot加1,直到Icoin变为0(本质上是将Icoin中的便士一次1个地转移到Itot中)。 之后,节点Q3移至下一个硬币(然后重复上述操作:设置映射器,再将价值转移到Itot中……)最后,我们需要一个退出条件来开启Cpwr。当Itot达到9时,即可满足该条件。 此速度解决方案与复杂度解决方案有很大不同。这里的思路是使用节点创建状态图,其中每个节点代表已找到的便士数量。 (复杂度 400 / 310,速度 9.75 / 20.08)

让我们以下列硬币序列为例:3便士、3便士、1便士、6便士、1便士 你从0(开始)处出发。 然后你发现一枚3便士硬币,切换到3便士节点(Q3)。 接着又发现一枚3便士硬币,于是移动到3+3=6节点(Q6) 之后发现一枚1便士硬币,移动到6+1=7节点(Q7) 再之后发现一枚6便士硬币,此时数值超过9,到达最终节点并“启动” 请注意,此解决方案达到了之前的速度预期(9.75)。在2022年12月24日的更新中,通过将速度要求提高至(20.08),该谜题的难度有所降低。 3-1二进制数转换器 速度 首先让我们看一下满足速度检查的最简单(你也可以称其为“笨拙”)的解决方案: (复杂度790/460,速度10.75/20.5)
在这里,我们正在测试红色部分的所有可能组合(数组数字是0还是1,向左走,现在是0还是1,向左走,等等),然后记录结果。速度极快,可能是你能构建的最快的装置。它能正常工作,但确实很无聊…… 复杂度 核心思路是将数组中的值计算为一个数字变量,然后显示其数值。 要将数组转换为数字变量,我们必须遍历数组的每个位置。这里提醒一下二进制基础知识:第一个位置的值为1(2^0),第二个位置为2(2^1),第三个位置为4(2^2),第四个位置为8(2^3)。 因此,我们需要分别将1、2、4或8加到计数器中。还记得之前那个需要添加1便士、3便士或6便士硬币的谜题吗?我们将使用相同的机制。但是……我们可用的计数器叫做积分器(Integrator),它最多只能计数到9,那么如何存储高达15的值呢?我们可以采用变通方法。你可能已经注意到,我们要处理的数字范围是0到15,所以最多需要写两位数。 第一位数字——如果存在的话——总是1。 第二位数字可以是任何值。这非常有用,因为第二位数字正好可以放入我们的积分器中。 至于剩下的那位数字,我们只需要记住是否达到了10以上。而游戏中有一个名为离合开关(Clutch Switch)的布尔变量可以用于此。 那么让我们总结一下所需的装置: 需要一个积分器单元(Iunit)和一个离合开关(C10)来表示最终数字。 我们需要记住当前在数组中的位置。这将是积分器艾波斯。我们需要知道是添加1、2、4还是8。此数值将存储在积分器Iadd中(复杂度430/460,速度35.75/20.5)。
最终算法如下: 拥有一个【便士计数器】(参见谜题2-8),能够为数组当前位置(存储在Ipos中)添加1、2、4和8。这是图中红色部分,其组成包括: 映射器,用于将位置(0、1、2、3)映射为其代表的十进制值(1、2、4、8)并输出到Iadd; 一个【便士计数器】(称为Qincrementor),用于同时增加Iunit并减少Iadd,直到Iadd达到0; 当Iunit达到最大值时,【Qreach10】节点将开启C10开关并重置Iunit计数器。 拥有显示最终值的方式,包括C10开关(简单写入)和Iunit(需要映射器进行写入)。这是图中绿色部分。 拥有移动到数组下一个位置的方式。这是图中黄色部分。如果所有位置都已处理完毕,我们就进入绿色部分显示结果。否则,我们在红色部分对其进行处理。 3-2 十进制数字转换器 此处所示解决方案的核心思路如下: TIN 上的输入值范围为 0 到 15。我们可以随时使用两个装置来保存此信息:一个装置用于存储最后一位数字,另一个装置用于存储第一位数字。 对于第一位数字,它要么是 1,要么不存在。这是一个布尔值。我们将其存储在名为 C10 的离合器开关中。 对于第二位数字,我们可以直接使用 TIN。我们只需确保光标位于该第二位数字上。然后,我们构建一个状态机,从数组的右侧(因为光标从右侧开始)向左遍历每个位置。在每个位置上,我们仅当输入值(如上文所述,存储在TIN和C10中)符合条件时,才将二进制位设为1。 0-15的二进制值分别为0000、0001、0010、0011、0100、0101、0110、0111、1000、1001、1010、1011、1100、1101、1110、1111。 我们可以看到,最右侧的二进制位仅在输入为1、3、5、7、9、11、13、15时被设置。简而言之,当且仅当(TIN中的)最后一位是奇数时。 左侧的位仅在输入为2、3、6、7、10、11、14、15时被设置。 下一位仅在输入为4、5、6、7、12、13、14、15时被设置。 最后,最左侧的位仅在输入为8或更高时被设置。 基于上述思路,让我们来构建自动机。绿色部分为初始化部分。我们将C10和TIN初始化为分别代表输入的第一位和第二位数字。黄色部分是初始化和处理之间的中间【集合点】。红色部分是处理过程,它通过Array.left对数组进行迭代,并且只有在满足前面所述条件时,才将每个位置设为1(复杂度400/430,速度15.75/15.37)。

速度与复杂度 之前的自动机虽然达到了复杂度要求,但速度不足。让我们对其稍作优化。 黄色的“rendez.-vous”(会合点)成本很高:每个测试用例都会经过它,但它实际上没有任何作用。因此,我们可以通过替换为条件判断来移除它。这样虽然更难理解,但能满足速度要求。 (复杂度 400 / 430,速度 13.75 / 15.37)

3-3 加法器 二进制加法与十进制加法非常相似:从最右侧的两个数字开始相加,如果结果超过单个数字的最大值(二进制中为1,而十进制中为9),则保留进位。然后移至左侧下一列,再次相加并计入进位,依此类推。 我们将按此方式操作:先对最右侧的数字(来自A1和A2)进行相加,写出结果(A3),然后向左移动,接着对A1和A2的下一组数字相加,写出结果后继续左移……每一步都需要考虑进位。 由于要相加的有三个数(来自A1的数字、来自A2的数字以及进位),每个数非0即1,因此总共有8种可能的组合。这足够小,可以用树形状态机来处理(而不是真的进行数值累加)。 速度 那么让我们尝试构建那个自动机吧。 (复杂度 390/360,速度 22.85/23.35)

检查当前列的各位数字之和(A1 + A2 + 溢出值)。对于前面提到的8种可能组合(红色的8条转换路径,若从起始节点开始则只有4条,因为初始溢出值为0),有4种可能结果(绿色的4个节点): 0 = 写入0,无溢出 1 = 写入1,无溢出 2 = 写入0,保留溢出 3 = 写入1,保留溢出 然后移动到左侧的下一列(即蓝色节点)并重复此过程,直至处理完所有5列。 最后,若溢出值已设置,则在A3的第六列写入最后一个1。这是黄色节点。 还有一点:我们需要知道是否已到达最后一列。因此,我们将使用一个积分器Ipos来存储位置,初始值为0。此Ipos检查已包含在转换路径中。复杂度 让我们对节点或转换的数量进行一些优化,以降低复杂度。首先,注意到有8个主要转换(红色的那些),但还有4个来自START节点的冗余转换。 我们希望避免这些来自START的转换,但又不能直接进入蓝色节点,否则就会直接进入下一列。因此,我们将蓝色节点拆分为两个节点:一个节点用于启动所有红色转换,另一个节点用于实际向左移动。 此更改增加了1个节点和1个连接这两个节点的转换。但我们节省了3个转换,因为现在从START节点出发的转换只有1个,而不是之前的4个。通过这种方式,我们节省了10个复杂度点。 (复杂度 380 / 360,速度 34.84 / 23.35)
接下来我们可以进行两项优化: 1. 顶部的绿色节点Qsum0的作用是将A3设为0并关闭溢出。但根据进入该节点的唯一条件,A3中的所有数字默认已为0,且溢出也已关闭。因此我们可以直接移除该节点,当满足条件时直接跳转到蓝色节点。这样可以额外节省20点复杂度。 2. 黄色节点仅执行A3.set(1)操作,而最后一个绿色节点也执行相同操作。我们可以将指向黄色节点的条件重定向到最后一个绿色节点,以达到相同结果。它还会开启溢出,然后再次回到蓝色节点,但仅此而已,不会影响最终结果(当你已经在左侧时再向左移动,实际上不会有任何动作,之后Ipos计数器会达到6,且不满足任何条件,因此会停止运行)。通过移除那个黄色节点,我们额外节省了10点复杂度。 以下是优化复杂度后的最终自动机: (复杂度 350 / 360,速度 35.05 / 23.35)

3-5 关系运算符 只有三种可能的输出:A小于B、A大于B或A等于B 我们可以设想一组简单的条件: 如果A=0且B=1到9:结果为A小于B 如果A=1且B=2到9:结果为A小于B 如果A=2且B=3到9:结果为A小于B ... 如果A=0且B=0:结果为A等于B 如果A=1且B=1:结果为A等于B 如果A=2且B=2:结果为A等于B ... 如果A=1且B=0:结果为A大于B 如果A=2且B=0到1:结果为A大于B 如果A=3且B=0到2:结果为A大于B ...依此类推。但这样会产生30个不同的条件,每个条件价值10个复杂度点。这意味着300点复杂度。再加上初始的100点,以及编写结果所需的一些额外点数,你已经超过400点复杂度了。如果我们采用这种方法,肯定能达到理想速度,但永远无法实现复杂度目标。 所以,我们来研究一个更有趣的复杂度解决方案。然后通过一些巧妙的调整,我们可以用比列出所有可能条件“更聪明”的方式来同时实现速度目标。 复杂度 这里提出的解决方案基于以下原理: 如果在等式(或不等式)两边同时加上或减去一个常数,等式(或不等式)仍然成立。例如,如果你有不等式7 < 9,你可以在两边同时减去7,得到0 < 2,这仍然是成立的。 在我们的自动机中,我们有一个等式(或不等式),其中A在一边,B在另一边。我们将利用刚才提到的特性,通过多次从A和B中各减去1(就像我们在之前的自动机中多次使用的循环那样),直到A或B中的一个变为0。经过此转换后,只会出现三种可能的情况,每种情况都可以用一个条件来表示:A和B都为0,这意味着A等于B;只有A为0,这意味着A小于B;只有B为0,这意味着A大于B。让我们用自动机来编写这个算法。(复杂度380/400,速度15.7/14.94)

绿色部分是初始化阶段。游戏不允许我们对初始寄存器进行递减操作,所以我们将RA和RB复制到两个积分器IRA和IRB中。不过在本文中,我会继续称它们为A和B,因为这样更简短。 红色部分是循环阶段,只要IRA和IRB都大于1,就会对这个(不)等式的两边进行递减。 蓝色部分则根据剩余的3种条件写入3种可能的结果。 关于速度,我们将使用与复杂度非常相似的算法。首先,我们要弄清楚在速度方面哪些部分成本较高,也就是我们算法的哪些部分需要大量步骤。观察以下示例: 若A=2且B=3,我们将对积分器进行2次递减操作,之后A将变为0。 若A=9且B=3,我们将对积分器进行3次递减操作,之后A将变为0。 若A=9且B=7,我们将对积分器进行7次递减操作,之后B将变为0。 若A=2且B=7,我们将对积分器进行2次递减操作,之后A将变为0。 从这些示例中可以看出,如果A和B的值都相当高,那么将其中一个值减至0会花费更多时间。 因此,以下解决方案的思路是根据第一个数字(A)的大小,将自动机分为两部分。 如果A的值较高(5、6、7、8或9),那么我们将不再进行递减操作,而是改为递增操作,直到其中一个值达到9。在最坏的情况下(若A=5),最多需要4次递增操作即可达到9。然后我们可以像之前那样写下结果。我们只需检查哪个积分器(A、B或两者)已达到9。如果A为低(0、1、2、3或4),那么我们将按照复杂度解决方案中的方法进行递减。这里最多也会递减4次以达到0。最终解决方案如下所示。现在有2个循环(红色部分),一个用于递增,另一个用于递减。(复杂度490/400,速度12.8/14.94)

尽管我们需要添加额外的初始条件来判断是递增还是递减,但通过将初始设计一分为二所节省的时间足以满足速度要求。
2026-03-20 22:00:35 发布在
Alan's Automaton Workshop
说点好听的...
收藏
0
0
