【Uva 題庫解題】CPE 二顆星選集 - part1
【Uva 題庫解題】CPE 二顆星選集 - part1
本筆記及該系列主要以每篇 5 題為主,也僅供筆者個人學習用途,斟酌參考。
若你喜歡本筆記,不妨為文章按下一顆愛心,或是追蹤我的個人公開頁,感謝您點入本篇筆記。
CPE 二顆星選集來源:https://par.cse.nsysu.edu.tw/~advprog/star.php
Uva 151 - Power Crisis
PDF Source:https://onlinejudge.org/external/1/151.pdf
Uva Online Judge:https://onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=87
Zerojudge:https://zerojudge.tw/ShowProblem?problemid=q891
該題為約瑟夫問題的變體。
題目架構:
- 共 個地區( 到 )
- 地區 總是第一個被切斷。
- 剩下 個地區,從地區 開始,每數到第 個地區就切斷它。
- 目標:找出最小的 ,使得地區 13 是最後一個被切斷的。
轉化成約瑟夫問題的形式:
首先由於地區 都是第一個被切斷的,只需考慮 的地區即可,因此就變成長度為 的約瑟夫問題。
另外為方便計算,將地區給重新編號:
- 地區
- 地區
- 地區
這樣就需要找到一個 ,使得在這 個人的環中,最後被切斷的索引為 11。
約瑟夫問題公式:$$f(n, m) =
\begin{cases}
0 & \text{if } n = 1 \
(f(n-1, m) + m) % n & \text{if } n > 1
\end{cases}$$
範例程式碼:
1 |
|
Uva 245 - Uncompress
PDF Source:https://onlinejudge.org/external/2/245.pdf
Uva Online Judge:https://onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=181
Zerojudge:https://zerojudge.tw/ShowProblem?problemid=e569
題目翻譯:
有一種用來建立文檔壓縮版本的簡單方法,可用於不包含任何數字字元的檔案。此壓縮方法需要先建立一份「未壓縮檔中出現過的單字」清單。
當在未壓縮檔中遇到非字母字元時,就直接將該字元複製到壓縮檔中。
當在未壓縮檔中遇到一個單字時,只有在該單字第一次出現時,才會將它直接複製到壓縮檔;並在此情況下,把該單字放到清單的最前端。
若不是第一次出現,則不把該單字複製到壓縮檔;改為把它在清單中的位置(編號)寫入壓縮檔,並將該單字移到清單最前端。清單位置的編號從 1 開始。
請撰寫一個程式,輸入為一個壓縮檔,輸出為原本未壓縮檔的還原結果。
解題思路:
基本上就是按照題目要求做事。
- 遇到單字時:直接印出該單字,把這個單字放到清單的最上面,原本清單裡的單字全部往後擠一位。
- 遇到數字時(如 3):數字 3 表示到清單裡面去找第 3 個單字,接著把那單字印出來,最重要的是,用完之後要把單字移到清單最上面去。
- 遇到標點符號或空格時:直接印出即可。
輸入方式可用 cin.get(ch),其他方式有可能會將標點符號或空格給讀掉(例如 sstream)。
透過 isalpha() 判斷字元是否為字母,以及 isdigit() 判斷字元是否為數字。
演算法流程(供參):
- 建立一個字串
buffer以及單字清單wlist。 - 用
cin.get(ch)一個一個讀入字元。 - 判斷為字母 ->
buffer += ch。 - 判斷為數字 ->
buffer += ch。 - 以上皆非,則先判斷
buffer是否為空。 buffer非空 -> 表示裡面有單字或是數字之類的。- 透過
isalpha()判斷buffer開頭是否為字母,得知是否為單字。是的話直接印出,並wlist.insert(wlist.begin(), buffer)把單字放到最前面去。 - 若不是字母,就表示他是一個數字,可用
stoi(buffer)轉整數,判斷是否為 0,是的話就結束,否則繼續。 - 若要繼續,則印出
wlist[數字 - 1]這個單字,接著把它刪了。wlist.erase(wlist.begin() + (數字 - 1)),然後再把單字插入到最前面。
- 透過
- 上述流程跑完後清空
buffer。 - 若上述沒有一個條件成立,就表示是標點符號或空格,直接輸出即可:
cout << ch。
範例程式碼:
1 |
|
Uva 255 - Correct Move
PDF Source:https://onlinejudge.org/external/2/255.pdf
Uva Online Judge:https://onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=191
Zerojudge:https://zerojudge.tw/ShowProblem?problemid=e601
題目翻譯:
我們有一個 64 個格子的正方形棋盤,從 0 到 63 編號,如圖 1 所示。
有兩個棋子:國王和皇后。國王所在格子與皇后所在格子這一對位置稱之為「國家」(state)。
- 如果兩個棋子不在同一個格子上,則該狀態是合法的(legal)。
- 國王與皇后輪流移動。
- 國王可以在水平方向或垂直方向走一步,只要它不會走到皇后所在的位置。
- 皇后可以在水平方向或垂直方向走一步或多步,只要它在移動途中不會碰到國王。
- 所有這些移動都稱為合法移動(legal)。
- 請注意,棋子不能沿對角線移動。

圖 1。
例如,假設我們有一個狀態:國王在第 17 格、皇后在第 49 格,如圖 2 所示。
國王的合法走法可以到第 9、16、18、25 格;而皇后合法地可以走到第 25、33、41、48、50、51、52、53、54、55 或 57 格。
不過,我們另外加上一個額外限制:棋子不可以移動到「另一個棋子也能移動到」的位置。
滿足這個限制的合法走法稱為「允許」(allowed)的走法。
在圖 2 中,國王與皇后各自透過一個允許走法所能到達的所有位置,分別用空心圓(○)與實心點(●)表示。
在圖 3 中,國王不能移動,它被困住了(locked in)。

圖 2。

圖 3。
此問題要你寫一個程式,對女王的移動進行一些檢查。
解題思路:
一個 棋盤,可以用以下的方法來做表示:
r = pos / 8c = pos % 8
接著就是去設計每一個條件的程式:
- Illegal state:當
k == q,國王跟皇后在同一格,表示狀態不合法。 - Illegal move:皇后走法不合法。
q == q2皇后都沒動,違反「至少動一步」的規則。- 判斷是否同列(
qr == nr)、同行(qc == nc),若皆非表示是斜著走,因此也不合法。 - 若同列或同行,沿移動方向(如
step = (nr > qr ? 1 : -1))一格一格掃過路徑(從下一格開始,含終點),若掃到k表示皇后會碰到國王,因此也不合法。
- Move not allowed:不能走到國王能走的格子。
- 若皇后新位置 q2 恰好是國王從 k 出發「上下左右一步」能到的格(即曼哈頓距離為 1),則輸出 Move not allowed。
- Continue / Stop:當皇后成功走到 q2(而且是 allowed)後,輪到國王行動,此時要判斷國王是否至少有一個 allowed 的一步。
- 國王能走的只有四步
(-1,0), (1,0), (0,-1), (0,1),因此窮舉這幾步。(r = kr + d[0], c = kc + d[1]) - 建立
k2 = r*8 + c表示新狀態的國王。- 檢查 k2 是否出界。
- 檢查是否
k2 == q2 - 檢查
k2是否為皇后在q2的位置可以走到的格(用queen_attacks(q2, k, k2)判斷)- 皇后能攻擊/到達的格:同列或同行,且從
q2走到該格的路徑上不能先撞到國王(國王會擋住皇后的直線)。
- 皇后能攻擊/到達的格:同列或同行,且從
- 若上述條件皆成立,表示
Stop國王被卡死了,否則Continue國王可繼續走。
- 國王能走的只有四步
範例程式碼:
1 |
|
Uva 294 - Divisors
PDF Source:https://onlinejudge.org/external/2/294.pdf
Uva Online Judge:https://onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=230
Zerojudge:https://zerojudge.tw/ShowProblem?problemid=d366
題目翻譯:
數學家喜歡研究各種數字的奇特性質。
例如,他們認為 945 是個有趣的數,因為它是第一個其因數總和大於它本身的奇數。
為了幫助他們尋找有趣的數字,你要撰寫一個程式,掃描一段數字範圍,並找出在該範圍內擁有最多因數的數字。
不幸的是,數字本身的大小以及範圍的大小,使得過於簡單的做法可能需要花太多時間才能跑完。
因此,請確保你的演算法足夠聰明,能在短短幾秒內處理最大的可能範圍。
筆者註:Divisors 稱為除數,也稱為我們最熟悉的因數。
解題思路:
要最快計算一個數字 的因數個數,最快的方法是使用「質因數分解」(Prime Factorization)。
假設 的標準分解式為:$$n = p_1^{a_1} \times p_2^{a_2} \times \dots \times p_k^{a_k}$$
則 的因數個數 為:$$d(n) = (a_1 + 1) \times (a_2 + 1) \times \dots \times (a_k + 1)$$
個數就是拿原本質因數的指數來做計算,至於 + 1 部分,則是表示指數的選法(0 ~ )。
解題步驟:
- 建立質數表(用埃拉托斯特尼篩法, Sieve of Eratosthenes):因為 最大為 ,只需測到 的質數即可,先預先篩選出 到 之間的所有質數。
- 區間掃描:對每組測資 和 ,遍歷 從 到 。
- 計算因數個數:對區間內的每個數字 :
- 用預存的質數表進行試除。
- 統計每個質因數的冪次(exponent)。
- 套用公式計算總因數個數。
- 若試除完所有 的質數後, 仍然大於 1,代表剩下的 本身就是一個大質數,因數個數需再乘以 。
- 找出最大值:若因數個數相同,題目要求輸出較小的那個數。
範例程式碼:
質數篩法優化部分:
1 | for (int i = p * p; i <= MAXSQRT; i += p) |
這段優化目的在於把目前質數 的所有倍數都標記為非質數(false)。
i += p ( 的倍數)即當找到質數 後,刪掉所有 的倍數: 等等。
這樣做的想法是如果 是 的倍數(且 ),那 肯定不是質數,因為能被 整除。
int i = p * p:在上層迴圈就將比 小的倍數都篩選過了,因此到這邊從 開始是合理的,不用從頭到尾遍歷一次,縮短耗時。
1 |
|
Uva 337 - Interpreting Control Sequences
PDF Source:https://onlinejudge.org/external/3/337.pdf
Uva Online Judge:https://onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=273
No Zerojudge
題目翻譯:
幾乎所有的文字模式終端(text-mode terminal)都是專用電腦系統,包含一個序列埠(serial port,用於與數據機或其他電腦系統通訊)、鍵盤、陰極射線管顯示器(CRT,cathode-ray tube),當然還有一顆微處理器(microprocessor)、一些隨機存取記憶體(RAM)與存放控制程式的唯讀記憶體(ROM)。
當一個字元從鍵盤或序列埠送到終端時,終端的軟體會將它分類為要顯示在 CRT 上的顯示字元(display character),或是引入控制序列(control sequence)的字元。控制序列用來指示終端執行特殊動作,例如清除螢幕、以指定方式移動游標,或改變字型等。
在此題中,假設你正在為一個小型終端撰寫軟體,該終端有一個 10 行 × 10 列的顯示(例如用於銷售點終端,point-of-sale terminal)。行(row)與列(column)編號皆為 0 到 9。引入控制序列的字元為「∧」(circumflex,插入符號)。緊接在該控制序列引導字元之後的一個字元(或在一種情況下的兩個字元)會指示你的軟體執行特殊功能。以下為你需要解讀的完整控制序列清單:
∧b
將游標移到當前行的開頭;游標的行(row)不變。
∧c
清除整個螢幕;游標的行與列皆不變。
∧d
若可能,將游標下移一行;游標的列(column)不變。
∧e
擦除游標所在行中游標列及其右側的字元;游標的行與列皆不變。
∧h
將游標移到第 0 行、第 0 列;螢幕內容不改變。
∧i
進入插入模式(insert mode,見下)。
∧l
若可能,將游標左移一列;游標的行不變。
∧o
進入覆寫模式(overwrite mode,見下)。
∧r
若可能,將游標右移一列;游標的行不變。
∧u
若可能,將游標上移一行;游標的列不變。
∧∧
在目前游標位置寫出一個脫字符「∧」,其行為與該字元並非特殊字元時相同;此寫入仍受目前模式(插入或覆寫)的影響。
∧##
將游標移到所指定的行與列;# 代表一個十進位數字;第一個 # 表示新的行號,第二個 # 表示新的列號。
系統保證不會送來非法的控制序列。游標不得移出允許的螢幕位置範圍(亦即介於第 0 行第 0 列到第 9 行第 9 列之間)。
當一個一般字元(非控制序列的一部分)送到終端時,其在螢幕上的顯示方式取決於終端的模式。
當終端處於覆寫模式(開機時即為覆寫模式)時,接收到的字元會取代游標位置處的字元。
若處於插入模式時,游標位置及其右方的字元會整體向右移一列,然後將新字元放在游標位置;游標所在行中最右邊的字元會因此遺失。
無論是哪一種模式,游標都會在可能的情況下向右移一列。
Input
輸入資料將包含多組對你終端軟體的測試。
每一組測試以一行整數 N 開始,接下來會有 N 行資料;這些資料中的每一個字元,都必須依照讀取的順序,視同輸入到你的終端軟體中來處理。
輸入資料中不會包含定位字元(tab),而輸入中的換行符號必須被忽略。
請注意,輸入資料中的空白字元(blank)是一般字元,必須顯示在終端螢幕上。
最後一組測試之後,會接著出現一行只包含整數 0 的資料,表示輸入結束。
任何控制序列都不會被拆分到兩行輸入資料之中。
在每一個測資開始時,請假設終端螢幕是清空的(也就是整個畫面都填滿空白字元)、終端處於覆寫模式(overwrite mode),且游標位於螢幕的第 0 行、第 0 列。
Output
對於每一個輸入測資,請輸出一行,包含該測資的編號(自 1 起依序編號),以及在完成該測資所有資料處理後,螢幕最終呈現的畫面內容。請將螢幕畫面以一個「方框」包圍;所需的輸出格式請參考下方範例說明。
Sample Input
1 | 7 |
Sample Output
1 | Case 1 |
解題思路:
這題其實沒有很難,就是題目長了點,基本上都按照題目的去做即可。
在思路上,可以先設定一個 vector <string> scr(10, string(10, ' ')) ,表示一個 的螢幕畫面。
接著 int r = 0, c = 0 表示目前的游標(cursor)位置。
可以建立兩個函數:putChar(ch)、clearScreen(),把程式碼包裝起來,後續就不用全擠在一個 for 迴圈裡面。
範例程式碼:
1 |
|



