← 全部題目
Solved UVa 解題

Zapping

頻道是 100 個點圍成的環,兩點距離為 d = |a − b|,順逆兩個方向分別是 d 和 100 − d,取小的即為答案。

Attempts2
First attempt2026-09-23
Solved2026-09-23

Problem

題目的主角很喜歡看電視,不過他從來不會乖乖停在同一台,會一直切到其他台。偏偏他家的狗把遙控器啃了,數字鍵全滅,只剩下「下一台」(△)和「上一台」(▽)兩顆按鈕還能用。想從第 3 台轉到第 9 台嗎?乖乖按 △ 六次吧。🐶🥵

而電視總共有 100 個頻道,編號 0 到 99,而且是循環的:

  • 在 99 台按 △ 會回到 0 台。

  • 在 0 台按 ▽ 會跳到 99 台。

給定現在的頻道 aa 和想去的頻道 bb,求最少要按幾下。

輸入有多組測資(最多 200 組),每行兩個整數 aa、bb,0≤a,b≤990 \le a, b \le 99,以一行 −1−1-1 -1 作為結束字元。


Approach

我一看到就把它畫成數線:aa、bb 是 0–99 上的兩個點,想從 aa 走到 bb 只有兩條路:

  1. 直接走:從 aa 走到 bb,距離 ∣a−b∣|a - b|。

  2. 繞過去:往反方向走到盡頭,從 99 跳回 0(或反過來),再走到 bb。

關鍵在於第二條路那個「跳回去」的動作也要按一下。把數線頭尾接起來看就很清楚:


0ab99 a 格b − a 格99 − b 格 1 格(99 → 0 繞回)

四段加起來是 a+(b−a)+(99−b)+1=100a + (b - a) + (99 - b) + 1 = 100,正好是 100 個頻道。數線上 0 到 99 只有 99 個間隔,第 100 個間隔就是那條虛線,也就是環的接縫。

所以令 d=∣a−b∣d = |a - b|,兩個方向的按鍵數分別是 dd 和 100−d100 - d,答案就是

min⁡(d, 100−d)\min(d,\ 100 - d)

因為取了絕對值,a>ba > b 還是 a<ba < b 都一樣,完全不用像我一開始那樣 swap。


什麼時候該繞路?

兩條路一條走 dd 格、一條走 100−d100 - d 格,加起來永遠是 100,也就是剛好一整圈。所以兩條路不可能同時都很長,一邊變長,另一邊就等量變短。(呈線性負相關)

要判斷繞路是否比較近,只要解這個不等式:

100−d<d  ⟺  100<2d  ⟺  d>50100 - d < d \iff 100 < 2d \iff d > 50

換成白話:

間距 dd比較該走哪條
d<50d < 50(不到半圈)d<100−dd < 100 - d直接走
d=50d = 50(剛好半圈)d=100−dd = 100 - d兩條一樣長
d>50d > 50(超過半圈)100−d<d100 - d < d繞過去

所以 50 就是分界點,也就是一圈 100 的一半。這個分界也直接解釋了我第一版為什麼只有 d≥50d \ge 50 會錯:舊公式用的是 99−d99 - d,等於把一圈當成 99,分界點被偷偷挪到 49.549.5。

  • d≤49d \le 49 時兩個版本都選「直接走」,答案都是 dd,錯誤完全被藏住。
  • d≥50d \ge 50 時舊版改選「繞過去」,而繞過去那條剛好少算了一格,錯誤才浮上來。


另一種寫法

如果想直接模擬「往上按幾下」和「往下按幾下」,可以用取模。往上是從 aa 走到 bb,往下是從 bb 走回 aa:

取模版
int up = (b - a + 100) % 100; // 一直按 △
int down = (a - b + 100) % 100; // 一直按 ▽
cout << min(up, down) << '\n';

先加 100 是因為 C++ 對負數取模會得到負數(例如 −6-6 % 100 == -6),加完再取模就能保證落在 00 到 9999。而 up + down 在 a≠ba \ne b 時一定等於 100,跟上面的 dd 和 100−d100 - d 是同一件事。


Wrong attempts

第一版我先 swap 讓 a≤ba \le b,然後另一條路寫成 99−b+a99 - b + a:

第一版(WA)
while (cin >> a >> b) {
if (a == -1 && b == -1) {
break;
}
if (a > b) {
swap(a, b);
}
if (a == 0 && b == 99) {
cout << "1\n";
} else {
cout << min(b - a, 99 - b + a) << '\n';
}
}

我少算了 99 → 0 那一格。題目寫的是 0 到 99,我腦中就直接拿 99 當一圈的長度,忘了它其實有 100 個頻道,太粗心了。😂


更好笑的是第 9–10 行那個特判。範例的 00 9999 用我的公式算出來是 min⁡(99, 99−99+0)=0\min(99,\ 99 - 99 + 0) = 0,明顯不對,我就直接寫死輸出 1。其實那是公式在報錯,我卻直接忽略了。它剛好是 b−a=99b - a = 99 的情況,少算一格的錯誤在這裡非常明顯,真的是事出必有因。


實際上只要間距 d=b−a≥50d = b - a \ge 50,繞過去那條就會變成比較短的一邊,少的那一格就會直接反映在答案上:

aabbdd正確 min⁡(d,100−d)\min(d, 100-d)我的 min⁡(d,99−d)\min(d, 99-d)結果
39666✅
1227151515✅
0999910 → 特判成 1✅ (但是是被特判蓋掉)
049494949✅
050505049❌
1080703029❌

三筆範例全過,其中一筆還是靠特判撐住的,難怪自己測不出來。WA 之後我想到 00 5050 這組,果然跑出 49,才發現是 99 和 100 的差別。

改完的差異只有這幾行:

修正
if (a > b) {
swap(a, b);
}
d = abs(a - b);
if (a == 0 && b == 99) {
cout << "1\n";
} else {
cout << min(b - a, 99 - b + a) << '\n';
cout << min(d, 100 - d) << '\n';

swap 和特判都不見了,特判能被刪掉,本身就是公式終於對了的證據,程式碼本身也優雅簡潔了許多(可讀性也更高)!

Solution

main.cc
6 collapsed lines
#include <iostream>
#include <algorithm> // min
#include <cstdlib> // abs
using namespace std;
void solve() {
int a, b;
int d;
while (cin >> a >> b) {
if (a == -1 && b == -1) {
break;
}
d = abs(a - b);
cout << min(d, 100 - d) << '\n';
}
}
8 collapsed lines
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
solve();
return 0;
}

被標起來的兩行就是全部:算出兩點距離,再跟繞一圈的另一邊比大小。


Sample Input

sample.in
3 9
0 99
12 27
-1 -1

Sample Output

sample.out
6
1
15

驗算:00 9999 的 d=99d = 99,另一邊是 100−99=1100 - 99 = 1,答案 1,不需要任何特判。

自己加的測資

extra.in
0 50
50 0
7 7
99 0
80 10
-1 -1

extra.out
50
50
0
1
30

分別測了剛好一半、a>ba > b、原地不動、繞一圈、以及 a>ba > b 又要繞路的情況。


錯誤歷程

  1. WA繞一圈的另一邊用 99 去扣,少算了 99 → 0 那一格;還特判了 0 99 把錯誤蓋掉#1
  2. AC改成 diff = |a − b|,答案 min(diff, 100 − diff),也不用再 swap 一次#2

See also