← 全部題目
Solved UVa 解題

Reverse and Add

反轉原本數字後相加,檢查是否為迴文,重複到成為迴文為止。

Attempts1
First attempt2026-09-29
Solved2026-09-29

Problem

反轉相加的操作是拿一個數,把它的數字順序反過來,再與原本的數相加。如果加出來的和不是迴文(從左讀和從右讀順序一樣),就對這個和重複同樣的操作。


以 195195 為例:

次數目前的數反轉相加結果
1195591786
27866871473
3147337415214
4521441259339

加了 44 次得到回文 93399339,所以輸出 44 93399339。


題目有提到大部分的數都會在幾步之內變成迴文,但也有例外:196 (剛好就是範例測資的下一個數) 是第一個到現在都沒有人找到迴文的數(但也還沒有人證明它永遠不會變成回文)。不過這題有保證所有測資:

  • 一定會得到迴文;
  • 少於 1000 次加法;
  • 最後的迴文不超過 4,294,967,2954,294,967,295(也就是 232−12^{32} - 1,咦? unsigned int 範圍?)。

輸入第一行是測資數 NN(0<N≤1000 < N \le 100),接著 NN 行各一個數 PP。每筆輸出「加法次數」和「最終得到的迴文」,中間一個空白。

Approach

沒有任何捷徑可走,直接模擬:

  1. 反轉 pp,得到 rev(p)\text{rev}(p)。
  2. p←p+rev(p)p \leftarrow p + \text{rev}(p),次數 +1+1。
  3. 如果 p=rev(p)p = \text{rev}(p),結束;否則回到 11。

而「是不是迴文」其實就是「反轉後是否跟自己一樣」,所以步驟 11 的反轉函式可以重複用來判斷。


讀入 p重複,直到 p 是迴文輸出 iteration 次數以及最終結果p += rev(p)次數 +1p 是迴文? 否是

會不會溢位呢?

題目保證最後的迴文 ≤232−1\le 2^{32} - 1。過程中的每個數都比最後的結果小(每一步都是加上一個正數),而 rev(p)\text{rev}(p) 又小於 p+rev(p)p + \text{rev}(p),所以整個過程中出現的所有數都不超過 232−12^{32} - 1。

型別上限夠不夠?
int231−1≈2.1×1092^{31} - 1 \approx 2.1 \times 10^9❌ 不夠
unsigned int232−1≈4.29×1092^{32} - 1 \approx 4.29 \times 10^9⚠️ 剛好夠,沒有餘裕
long long263−1≈9.2×10182^{63} - 1 \approx 9.2 \times 10^{18}✅ 很安全


本來想用 unsigned int,結果沒有現在才發現居然沒有 stoui?

unsigned int 的上限剛好就是 232−12^{32} - 1,看起來完美貼合題目限制,所以我原本想用它。寫到一半才發現,標準庫的字串轉整數只有這幾個:


函式回傳型別
stoiint
stollong
stoulunsigned long
stolllong long
stoullunsigned long long

好吧,沒有 stoui。上網查了一下,這些函式都是包裝 C 的 strtol 系列,而 C 本來就只有 strtoul(unsigned long)和 strtoull,沒有回傳 unsigned int 的版本。


真的要用 unsigned int 的話,有兩條路:

方法 1:stoul 再轉型
unsigned x = stoul(p) + stoul(p_rev);
方法 2:不再使用字串
unsigned p;
cin >> p; // cin 可以直接讀進 unsigned

用方法 1 時要注意,stoul 回傳的 unsigned long 大小會隨平台改變:

平台資料模型unsigned long
Linux / macOSLP6464 位元
WindowsLLP6432 位元

這題的數都不超過 232−12^{32} - 1,所以兩種平台都還行,但寫 Cross-platform 程式時不能假設 long 一定是 64 位元。

所以結論是:在 online judge 上用 long long 或 unsigned long long 比較保險,多出來的 4 bytes 對這題完全沒有影響。


至於反轉時的前導零要怎麼處理?

反轉 750 會得到 057,也就是 57。不過以下提供的兩種寫法都處理掉了:

  • 字串版經過 stoll(“057”) 後會得到 57。
  • 數字版的話則是 r = r * 10 + x % 10 反轉後,開頭的 00 本就不會留下來。

判斷迴文時也不會誤判:“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));

whiledo-whilep 是迴文?加一次0 次以上加一次p 是迴文?至少 1 次 否是否是

兩張圖唯一的差別是菱形的位置:while 把檢查放在加法前面,do-while 放在後面。


這兩種寫法只有在輸入本身就是迴文時會不一樣。以輸入 11 為例:

寫法輸出說明
while0 1一開始就是迴文,一次都沒加
do-while1 2先做一次 1+1=21 + 1 = 2,22 是迴文

若按照題目的字面定義,要用 do-while 才對。不過我的 while 版一次 AC,代表 online judge 的測資裡應該沒有本身就是迴文的輸入,所以這個差別沒有被測到。

Solution

main.cpp
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

sample.in
3
195
265
750

Sample Output

sample.out
4 9339
5 45254
3 6666

Refinement

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 為例:

xx % 10r
123444
123343
122432
114321
0—結束

Alternatives

然而,字串版還是有它的用處。數字版的前提是「結果放得進 64 位元」。但如果題目拿掉 ≤232−1\le 2^{32} - 1 的保證,比如要你算到幾百位數,數字版就完全不能用了。

這時候字串版只要把 stoll + to_string 換成逐位的大數加法,就能處理任意長度。

雖然這題用不到,但題目提及的 196196 這種算了幾百萬步還沒變迴文的研究,用的就是這類大數運算寫法。


錯誤歷程

  1. AC用字串反轉、stoll 相加、to_string 轉回,while 迴圈直到迴文#1

See also