【TIS-100 攻略】TIS-NET 第 16 关:反向索引查询器

本文首发于 B 站《TIS-100》文集(https://www.bilibili.com/read/readlist/rl626023)。原创不易,转载请注明出处。
TIS-NET 第 16 关《反向索引查询器》(Back Reference Reifier)关卡展示

本关的 IN.A 会不断给你提供一些数字,然后:
当你从 IN.R 里收到 0 时,你需要向 OUT 输出 IN.A 中的当前数字;
当你从 IN.R 里收到 -1 时,你需要向 OUT 输出 IN.A 中的前 1 个数字;
当你从 IN.R 里收到 -2 时,你需要向 OUT 输出 IN.A 中的前 2 个数字;
当你从 IN.R 里收到 -3 时,你需要向 OUT 输出 IN.A 中的前 3 个数字;
当你从 IN.R 里收到 -4 时,你需要向 OUT 输出 IN.A 中的前 4 个数字。
IN.R 的最小值为 -4,也就是最多查询到前 4 个数字。
我们可以借用第一章第 18 关《信号窗口筛选器》里的【滑动窗口】算法思想,维护最近 5 个值,并选择请求的值输出即可(传送门:【TIS-100 攻略】第 18 关:信号窗口筛选器)。问题在于,本关的版面上没有提供任何栈,所以我们只能通过节点来存储这最近的 5 个值。设当前值为 a[0],前 4 个值分别为 a[-1] ~ a[-4],那么以下的示意图里提供了一种维护最近 5 个值的方法:









至此:
当前数字变为了 a[1];
节点 A 的 acc 为当前数字的前 2 个数字 a[-1],bak 为当前数字的前 1 个数字 a[0];
节点 B 的 acc 为当前数字的前 4 个数字 a[-3],bak 为当前数字的前 3 个数字 a[-2]。将以上下标全部减去 1,我们就又回到了第一步时的样子。
节点 C 会依次收到 a[-4] ~ a[0],但我们只能选择其中 1 个量输出,其余 4 个量都要丢弃。那么我们在输出之前,要丢弃掉多少个量呢?
当 R 为 0 时,我们要输出 a[0],要丢弃前方的 a[-4] ~ a[-1] 共 4 个量;
当 R 为 -1 时,我们要输出 a[-1],要丢弃前方的 a[-4] ~ a[-2] 共 3 个量;
当 R 为 -2 时,我们要输出 a[-2],要丢弃前方的 a[-4] ~ a[-3] 共 2 个量;
当 R 为 -3 时,我们要输出 a[-3],要丢弃前方的 a[-4] 共 1 个量;
当 R 为 -4 时,我们要输出 a[-4],没有量可以丢弃。
因此,输出前要丢弃的量 n 和 R 的关系为:

如果输出的不是 a[0],那么输出完毕后,剩余的量也要丢弃。不过这部分量我们可以用【监测哨兵】的方法来丢弃:上方的节点将 a[-4]~a[0] 全部发送完毕后,最终发送一个哨兵 0。当节点 C 接收到最终的哨兵 0 时,停止丢弃。本关的思路就是这样,代码如下:

IN.R 下方的节点将收到的 R 值传给右边的 A 节点(mov up right)。
A 节点和 B 节点首先接力将本次的 R 传给 C 节点(mov left down, mov up down)。接下来 A 节点和 B 节点按照上方示意图的指示,向 C 节点发送 a[-4] ~ a[0] 的值。
首先是 B 节点,先传 acc 里的 a[-4](mov acc down),并接收 A 节点传来的 a[-2],覆盖掉原先的 a[-4](mov up acc)。然后传 bak 里的 a[-3](swp, mov acc down, swp)和 acc 里的 a[-2](mov acc down)。接着,A 节点会将 a[-1] 和 a[0] 发过来,我们原封不动地将这两个数传下去(mov up down, mov up down)。最后,发送哨兵 0(mov 0 down),并将 acc 和 bak 交换(swp)。
A 节点简单一些,先传 acc 里的 a[-2](mov acc down),然后从 IN.A 接收 a[0],覆盖掉原先的 a[-2](mov up acc),接着传 bak 里的 a[-1](swp, mov acc down, swp)和 acc 里的 a[0](mov acc down),最后将 acc 和 bak 交换(swp)。
C 节点按照计划,首先丢弃前 4+R 个量:
将 acc 设为 4+R(mov 4 acc)
(add up)
若 4+R 为 0,则不丢弃任何量,直接跳到第 7 行(jez 7),
否则每丢弃一个量(mov up nil),
就令 acc 减去 1(sub 1),
一直如此做直到 acc 减到 0 为止(jnz 4)。
将该丢弃的量悉数丢弃后,将下一个量送到输出口(mov up down),
然后继续丢弃剩下的量(mov up acc),
直到遇到哨兵 0 为止(jnz 8)。
点击左下角的【RUN】,稍等片刻,便会弹出结算界面:

也许我以上说的前 4 个数的维护方案不是最优的,我的运行时长离直方图最左端还有不少距离。如果读者有更好的维护方案,请留言告诉我。