【TIS-100 攻略】TIS-NET 第 21 关:质因数计算器

本文首发于 B 站《TIS-100》文集(https://www.bilibili.com/read/readlist/rl626023)。原创不易,转载请注明出处。
TIS-NET 第 21 关《质因数计算器》(Prime Factor Calculator)关卡展示

本关要求列出 IN 里提供的数字的所有质因数,将这些质因数依次输出,并以 0 作为结束标记。IN 里的数字一定在 2~99 范围内。
我们首先想到的是将 2~99 中的所有质数列出来,并令原数依次和这些数做除法运算,检查能否被指定的质数整除。但是 2~99 范围内有 25 个质数,一个节点根本写不下。我们必须要想办法缩减这张质数表的大小。
这里先说一个结论:2~99 范围内的数,要么是质数,要么一定能被 2、3、5、7 中的任何一个数整除。因为不含 2、3、5、7 这四个质因子的最小合数为 11×11=121。因此原始数字我们只需要判断是否能被 2、3、5、7 整除即可。2~99 范围内,如果一个数字不能被 2、3、5、7 中的任何一个数整除,那么它要么是 1,要么是质数。我们的质数表里只需要列出 2、3、5、7 这四个质数就 OK 了。
本题的算法如下:
从 in 读入一个数字 a;
检查 a 能否被 2 整除,若能,则将 a 除以 2,并向输出口输出 2 这个因子,然后反复执行此步,直到 a 不能被 2 整除为止;
检查 a 能否被 3 整除。若能,则将 a 除以 3,并向输出口输出 3 这个因子,然后反复执行此步,直到 a 不能被 3 整除为止;
检查 a 能否被 5 整除。若能,则将 a 除以 5,并向输出口输出 5 这个因子,然后反复执行此步,直到 a 不能被 5 整除为止;
检查 a 能否被 7 整除。若能,则将 a 除以 7,并向输出口输出 7 这个因子,然后反复执行此步,直到 a 不能被 7 整除为止;
至此,a 不是 1 就是一个大于 7 的质数。仅当 a 不为 1 时,输出最后的 a。
本关的代码如下:

上方节点首先将本轮的 a 向下传(mov up down),并不断提供 2、3、5、7 的质因子(mov 2 down, mov 3 down, mov 5 down, mov 7 down),用于检查 a 是否能被这些数整除。最后提供一个 0 的结束信号(mov 0 down)。
中央节点依次计算 a 能否被 2、3、5、7 整除:
它将收到的 a 放入 bak(mov up acc)
(swp)
并从上方的质数表里读入一个 b,放入 acc(mov up acc)。
如果从上边收到了 0,说明到达了质数表的末端,跳到第 13 行执行(jez d)。
尚未收到 0 时,将 b 和 a 依次发给右边的节点(mov acc right)
(swp)
(mov acc right),让右边计算 a 能否被 b 整除,并接收右边的反馈信号:
当 a 不能被 b 整除时,你会收到 -6 信号,此时向上跳 6 行,从质数表里读入一个新的 b,然后计算 a 能否被新的 b 整除。
当 a 能被 b 整除时,你会收到 1 信号,同时右边会把 a 除以 b 的商 q 发过来。此时向下跳 1 行,用 q 覆盖 a(mov left acc),
并将当前的有效质因子 b(swp)
向下发送(mov acc down)。
最后跳回第 5 行继续计算新的 a 能否被 b 整除(jmp 5)。
如此反复,直到所有的质因子 b 都被尝试过了以后,a 会变成 1 或一个大于 7 的质数。此时跳到第 13 行,将最后的 a(swp)
向下发送(mov acc down)。
右边的两个节点用于配合计算 a 除以 b 的值。其中中间靠右的节点用来计算余数 r,右下角的节点用来反复提供除数 b,以及计算商 q。
中间靠右的节点将收到的除数 b 发给左下角(mov left down),
然后将被除数 a 放入 acc,作为 r 的初始值(mov right acc)。
接下来,每将 r 减去一次 b(sub down),
就判断 r 的符号。是正号时按顺序执行,是负号时跳到第 12 行(jlz c),
是 0 时跳到第 8 行(jez 8)。
r 是正号时,向下发送 -4 让下方保留住当前的 b(mov -4 down),
然后跳回第 3 行继续减(jmp 3);
r 是 0 时,说明 a 能被 b 整除,向下发送 1 令下方将当前的商传上来(mov 1 down),
向左发送 1 表示当前因子有效(mov 1 left),
并将得到的商 q 一并发给左边,作为下一轮的 a(mov down left);
(jmp 1)
r 是负号时,说明 a 不能被 b 整除,向左发送 -6 表示当前因子无效(mov -6 left),
向下发送 3 令下方将本轮的 b 和 q 丢弃(mov 3 down)。
现在来看反复提供除数以及计算商的右下角节点:
右下角的节点收到 b 后(mov up acc),
每发送一次 b(mov acc up),
就令 bak 里的商 q 加上 1(swp)
(add 1)
(swp)
然后听从上方的指令(jro up):收到 -4 时,说明当前除法尚未计算完成。我们保留住当前的 b 和 q,向上跳 4 行,继续执行发送 b 和 q+1 的任务;
收到 1 时,说明 a 能被 b 整除。我们向下跳 1 行,将计算好的 q(swp)
发给上方(mov acc up),作为下一次除法计算的 a 值,然后清除 b 和 q。
收到 3 时,说明 a 不能被 b 整除。我们向下跳 3 行,直接清除掉本轮的 b(sub acc)
和 q(swp)
最后是下方居中的输出节点。这个节点会不断收到正中央传来的 1 或质因子。如果收到的是 2、3、5、7 中的一个,说明还有 1 或别的质因子,我们不能输出末端的 0。直到收到最终的 1 或一个大于 7 的质因子时,我们才能输出末端的 0。我们依次检查:
如果收到的数字是 1(mov -1 acc)
(add 1)
则不输出该 1,直接跳到第 8 行输出序列末端的 0(jez 8, mov 0 down)。
收到的数字是大于 1 的数字时,说明这是一个质因子,我们将其输出(add 1)
(mov acc down)
然后紧接着判断:如果该质因子是 2、3、5、7 中的一个,则尚未到达序列末端。因此,如果该质因子小于 11(sub 11),
则跳回第 1 行继续等待下一个质因子(jlz 1),
直到得到了 1 或一个大于等于 11 的质因子后,我们输出序列末端的 0(mov 0 down)。
点击左下角的【RUN】,稍等片刻,便会弹出结算界面:
