Reverse and Add
反轉原本數字後相加,檢查是否為迴文,重複到成為迴文為止。
| Attempts | 1 |
|---|---|
| First attempt | 2026-09-29 |
| Solved | 2026-09-29 |
Problem
反轉相加的操作是拿一個數,把它的數字順序反過來,再與原本的數相加。如果加出來的和不是迴文(從左讀和從右讀順序一樣),就對這個和重複同樣的操作。
以 為例:
| 次數 | 目前的數 | 反轉 | 相加結果 |
|---|---|---|---|
| 1 | 195 | 591 | 786 |
| 2 | 786 | 687 | 1473 |
| 3 | 1473 | 3741 | 5214 |
| 4 | 5214 | 4125 | 9339 |
加了 次得到回文 ,所以輸出 。
題目有提到大部分的數都會在幾步之內變成迴文,但也有例外:196 (剛好就是範例測資的下一個數) 是第一個到現在都沒有人找到迴文的數(但也還沒有人證明它永遠不會變成回文)。不過這題有保證所有測資:
- 一定會得到迴文;
- 少於 1000 次加法;
- 最後的迴文不超過 (也就是 ,咦? unsigned int 範圍?)。
輸入第一行是測資數 (),接著 行各一個數 。每筆輸出「加法次數」和「最終得到的迴文」,中間一個空白。
Approach
沒有任何捷徑可走,直接模擬:
- 反轉 ,得到 。
- ,次數 。
- 如果 ,結束;否則回到 。
而「是不是迴文」其實就是「反轉後是否跟自己一樣」,所以步驟 的反轉函式可以重複用來判斷。
會不會溢位呢?
題目保證最後的迴文 。過程中的每個數都比最後的結果小(每一步都是加上一個正數),而 又小於 ,所以整個過程中出現的所有數都不超過 。
| 型別 | 上限 | 夠不夠? |
|---|---|---|
| int | ❌ 不夠 | |
| unsigned int | ⚠️ 剛好夠,沒有餘裕 | |
| long long | ✅ 很安全 |
本來想用 unsigned int,結果沒有現在才發現居然沒有 stoui?
unsigned int 的上限剛好就是 ,看起來完美貼合題目限制,所以我原本想用它。寫到一半才發現,標準庫的字串轉整數只有這幾個:
| 函式 | 回傳型別 |
|---|---|
| stoi | int |
| stol | long |
| stoul | unsigned long |
| stoll | long long |
| stoull | unsigned long long |
好吧,沒有 stoui。上網查了一下,這些函式都是包裝 C 的 strtol 系列,而 C 本來就只有 strtoul(unsigned long)和 strtoull,沒有回傳 unsigned int 的版本。
真的要用 unsigned int 的話,有兩條路:
unsigned x = stoul(p) + stoul(p_rev);unsigned p;cin >> p; // cin 可以直接讀進 unsigned用方法 1 時要注意,stoul 回傳的 unsigned long 大小會隨平台改變:
| 平台 | 資料模型 | unsigned long |
|---|---|---|
| Linux / macOS | LP64 | 64 位元 |
| Windows | LLP64 | 32 位元 |
這題的數都不超過 ,所以兩種平台都還行,但寫 Cross-platform 程式時不能假設 long 一定是 64 位元。
所以結論是:在 online judge 上用 long long 或 unsigned long long 比較保險,多出來的 4 bytes 對這題完全沒有影響。
至於反轉時的前導零要怎麼處理?
反轉 750 會得到 057,也就是 57。不過以下提供的兩種寫法都處理掉了:
- 字串版經過 stoll(“057”) 後會得到 57。
- 數字版的話則是 r = r * 10 + x % 10 反轉後,開頭的 本就不會留下來。
判斷迴文時也不會誤判:“750” 和 “057” 不相等,750 和 57 也不相等,因為位數上就有差異。
我在寫的時候其實有在想要用 while 還是 do-while?
題目的描述其實是「先反轉相加,如果和不是回文再重複」,也就是至少會做一次加法,然後才檢查。我一開始寫的 AC 版用的是 while,會先檢查再加:
while (p != rev(p)) { // 先檢查 p += rev(p); ++it;}
do { // 先加,再檢查 p += rev(p); ++it;} while (p != rev(p));兩張圖唯一的差別是菱形的位置:while 把檢查放在加法前面,do-while 放在後面。
這兩種寫法只有在輸入本身就是迴文時會不一樣。以輸入 為例:
| 寫法 | 輸出 | 說明 |
|---|---|---|
| while | 0 1 | 一開始就是迴文,一次都沒加 |
| do-while | 1 2 | 先做一次 , 是迴文 |
若按照題目的字面定義,要用 do-while 才對。不過我的 while 版一次 AC,代表 online judge 的測資裡應該沒有本身就是迴文的輸入,所以這個差別沒有被測到。
Solution
5 collapsed lines
#include <iostream>#include <string>
using namespace std;
void solve() { int n; string p; string p_rev; long long x;
while (cin >> n) { for (int i = 1; i <= n; i++) { int iteration = 0;
cin >> p;
p_rev = string(p.rbegin(), p.rend());
while (p != p_rev) { x = stoll(p) + stoll(p_rev); p = to_string(x); p_rev = string(p.rbegin(), p.rend()); iteration++; }
cout << iteration << ' ' << p << '\n'; } }}9 collapsed lines
int main() { ios::sync_with_stdio(false); cin.tie(nullptr);
solve();
return 0;}第 18 行我用反向迭代器 rbegin()、rend() 直接建出反轉後的字串。第 20~25 行是主迴圈:兩個字串轉成 long long 之後相加,再轉回字串、重新反轉,直到兩者相等。
Sample Input
3195265750Sample Output
4 93395 452543 6666Refinement
AC 之後回來看,有幾個地方可以更精簡。
其實不用字串,直接用數字反轉?
既然整個過程都在 64 位元整數的範圍內,就不需要在字串和數字之間來回轉換。原本每一輪要做兩次 stoll、一次 to_string,再建一次反轉字串,其實不必,改成純數字運算後只會剩下除法和取餘數的運算。
unsigned long long rev(unsigned long long x) { unsigned long long r = 0; while (x != 0) { r = r * 10 + x % 10; x /= 10; } return r;}每輪把 x 的個位數接到 r 後面,再把 x 的個位數去掉。以 1234 為例:
| x | x % 10 | r |
|---|---|---|
| 1234 | 4 | 4 |
| 123 | 3 | 43 |
| 12 | 2 | 432 |
| 1 | 1 | 4321 |
| 0 | — | 結束 |
Alternatives
然而,字串版還是有它的用處。數字版的前提是「結果放得進 64 位元」。但如果題目拿掉 的保證,比如要你算到幾百位數,數字版就完全不能用了。
這時候字串版只要把 stoll + to_string 換成逐位的大數加法,就能處理任意長度。
雖然這題用不到,但題目提及的 這種算了幾百萬步還沒變迴文的研究,用的就是這類大數運算寫法。
錯誤歷程
- AC用字串反轉、stoll 相加、to_string 轉回,while 迴圈直到迴文#1
See also
- Lychrel number(Wikipedia):196 這類數的名稱