【TIS-100 攻略】TIS-NET 第 10 關(guān):序列眾數(shù)計算器

本文首發(fā)于 B 站《TIS-100》文集(https://www.bilibili.com/read/readlist/rl626023)。原創(chuàng)不易,轉(zhuǎn)載請注明出處。
TIS-NET 第 10 關(guān)《序列眾數(shù)計算器》(Sequence Mode Calculator)關(guān)卡展示

本關(guān)的 IN 會不斷給你一些以 0 結(jié)尾的序列,你需要找出序列中的眾數(shù),即出現(xiàn)次數(shù)最多的那個數(shù)。若有多個數(shù)的出現(xiàn)次數(shù)并列第一,則改為輸出 0。
本關(guān)需要用到類似于計數(shù)排序的思想:每讀入一個數(shù) num,就將該數(shù)的頻數(shù) f[num]?加上 1,并和歷史最大頻數(shù) maxF 比較:
若 f[num] < maxF,輸出值 ans 不變;
若 f[num] = maxF,輸出值 ans 歸零;
若 f[num] > maxF,輸出值 ans 強制更改為 num,且令 maxF 加 1。
讀完序列中的所有數(shù)字后,輸出 ans,并將 maxF 歸零。示意圖如下所示:







C 語言代碼如下:
本題的輸入量不會超過 5,因此 f 數(shù)組最大下標(biāo)到 5 就足夠了。轉(zhuǎn)寫成的 TIS-100 代碼如下:

我們在開始分析前要初始化一個長度為 5 的 f 數(shù)組,分析結(jié)束后要將數(shù)組銷毀。2 號節(jié)點干的就是這樣的事:
首先用循環(huán)的方法向左上角的棧里塞入 5 個 0,分別是 f[5] ~ f[1] 的初始值(mov 5 acc)
(mov 0 up)
(sub 1)
(jnz 2)
塞完后向 3 號節(jié)點發(fā)送一個 1 的啟動信號(mov 1 right)。
本輪分析完成后,2 號節(jié)點會從 3 號節(jié)點收到一個 5(mov right acc),
這時候我們再將左上角棧里的 f[1] ~ f[5] 取出并銷毀(mov up nil)
(sub 1)
(jnz 7)
2 號節(jié)點就做兩件事——初始化,以及銷毀。然后我們來看 1 號節(jié)點。
1 號節(jié)點每從 in 收到一個數(shù) num(mov up acc),
就將這個 num 向下傳一份(mov acc down),然后判斷:
如果收到的 num 是 0,說明到達了序列末端,跳回第 1 行,準(zhǔn)備開始分析下一個序列(jez 1)。
收到的 num 不為 0 時,需要將 f[num] 加上 1,并將改寫后的 f[num] 值也發(fā)給下方。我們先將 num 值復(fù)制一份到 bak 里備用(sav),
然后從左上角里彈出前 num 個數(shù)字 f[1] ~ f[num],將這些數(shù)字全部送往右上角的棧暫存(mov left right)
(sub 1)
(jnz 5)
至此,右上角的棧的棧頂便是 f[num] 的值,我們將它取出(mov right acc),
加上 1(add 1)
并發(fā)給下面后(mov acc down),
放回原處(mov acc right)。
更新完 f[num] 的值后,我們要將放在右上角棧里的 f[num] ~ f[1] 共 num 個數(shù)字放回左上角的棧里(swp)
(mov right left)
(sub 1)
(jnz d)
3 號節(jié)點的 acc 用來接收上方發(fā)來的 num,bak 用來存儲眾數(shù)?ans:
3 號節(jié)點首先需要等待 2 號節(jié)點完成對 f 數(shù)組的初始化過程,將 5 個 0 放入左上角的棧,并發(fā)來一個 1 后才能啟動(jro left)。
啟動后,我們會從 1 號節(jié)點處收到 num 的值(mov up acc),判斷它是否是 0。
收到的 num 為 0 時,說明到達了序列末端,跳到第 10 行執(zhí)行(jez a)。
收到的數(shù)字不為 0 時,我們先給 4 號節(jié)點發(fā)一個 3 信號(mov 3 right),
然后將上方發(fā)來的 f[num] 交給 4 號節(jié)點(mov up right),
并根據(jù) 4 號節(jié)點的反饋(jro right)執(zhí)行不同的任務(wù):當(dāng) f[num] < maxF 時,4 號節(jié)點會發(fā)來一個 -4,我們往上跳 4 行,不更新 bak 里存放的 ans 的值,直接繼續(xù)接收下一個 num;
當(dāng) f[num] = maxF 時,4 號節(jié)點會發(fā)來一個 1,我們往下跳 1 行,將 bak 里的 ans 強制置零(sub acc)
當(dāng)?f[num] > maxF 時,4 號節(jié)點會發(fā)來一個 2,我們往下跳 2 行,將 bak 里的 ans 更新為當(dāng)前的 num 值(sav)
(jmp 2)
如此往復(fù),直到從 1 號節(jié)點收到 0 后,我們跳到第 10 行執(zhí)行。注意我上面分析 2 號節(jié)點時說的:【本輪分析完成后,2 號節(jié)點會從 3 號節(jié)點收到一個 5,這時候我們再將左上角棧里的 f[1] ~ f[5] 取出并銷毀?!窟@時候我們自然是給 2 號節(jié)點發(fā)這個?5,讓它開始清理左上角的棧(mov 5 left)。
然后我們將 bak 里的 ans 值(swp)
往下傳(mov acc down),
最后給 4 號節(jié)點發(fā)一個 1 信號(mov 1 right)。
4 號節(jié)點則是用來維護實時的 maxF 的,它的 bak 用來存儲實時的 maxF,acc 則是用來和發(fā)來的 f[num] 做差值運算。每次計算完成后,acc 和 bak 的值要保持一致,以便隨時準(zhǔn)備下一次差值運算。
4 號節(jié)點會從 3 號節(jié)點收到兩種信號,3 信號和 1 信號(jro left)。
收到 1 信號時,說明 3 號節(jié)點已經(jīng)分析完了當(dāng)前序列,我們向下跳 1 行,把 acc?清零(sub acc),
然后直接跳到第 15 行(jmp f),執(zhí)行 sav 指令,將表示 maxF 的 bak?清零,準(zhǔn)備分析下一個序列里的眾數(shù)。
收到 3 信號后,3 號節(jié)點會緊跟著發(fā)來當(dāng)前的 f[num]。我們往下跳 3 行,將 maxF 與 f[num] 做差(sub left),并根據(jù)差值的符號選擇不同的代碼塊執(zhí)行:
差值為負時按順序執(zhí)行,差值為正時跳到第 13 行執(zhí)行(jgz d),
差值為 0 時跳到第 11 行執(zhí)行(jez b)。
差值為負時,說明 maxF < f[num],按照規(guī)則,此時需要更新 ans 和 maxF。因此,給 3 號節(jié)點發(fā)送 2,令其更新 ans(mov 2 left),
然后將自己的 maxF(swp)
加上 1(add 1)
并覆蓋 acc(jmp f, sav)。
差值為 0 時,說明 maxF = f[num],按照規(guī)則,此時需要將 ans 歸零,maxF 不變。因此,給 3 號節(jié)點發(fā)送 1,令其清零 ans(mov 1 left),
然后自己的 maxF 保持不變,并覆蓋 acc(jmp e, swp, sav);
差值為正時,說明 maxF > f[num],按照規(guī)則,此時 ans 和 maxF 均不變。因此,給 3 號節(jié)點發(fā)送 -4,讓其直接從 2 號節(jié)點接收下一個 num(mov -4 left),
然后自己的 maxF 保持不變,并覆蓋 acc(swp)
(sav)
點擊左下角的【RUN】,稍等片刻,便會彈出結(jié)算界面:
