[CPE一星49題] UVA948 - Fibonaccimal Base 題目解析與實作


編輯製作:
蔡豐聲、莊祺仁 

單位: 中國醫藥大學 醫療資訊學系 程式俱樂部

 YT: https://youtu.be/ph0WJF3h0Qw

## 題目資訊

  • 題目名稱: Fibonaccimal Base
  • 編號: CPE10401, UVA948 

## 題目解析

有名的費氏數列(Fibonacci sequence)是以 0 和 1 開始,然後把最後的兩個數字相加以得到下一項。例如數列的第三項為 1 ($1=1+0$),第四項為 2 ($2=1+1$),第五項為 3 ($3=2+1$),依此類推。

本題要求我們使用「費氏進位法(Fibonaccimal Base)」來表示一個正整數。齊肯多夫定理(Zeckendorf's Theorem)指出:任何正整數都可以唯一分解成若干個不連續的費氏數之和。我們用一連串的 0 與 1 來表示,其中 1 表示有用到該項費氏數,0 表示沒有用到。為了維持唯一性,不可以連續使用費氏數列中相鄰的項(例如不能同時使用 2 和 3,因為 $2+3=5$,應該直接使用 5)。

舉例來說:
$17 = 13 + 3 + 1$ (對應費氏數列中的項,且沒有相鄰項)
將有使用到的項記為 1,沒用到的記為 0,由左至右排列,因此 $17 = 100101 \text{ (fib)}$。

## 輸入說明

  • 輸入的第一行含有一個數字 $N$,代表以下有幾個數字 ($1 \le N \le 500$)。

  • 接下來有 $N$ 行,每行有一個小於 100000000 的正整數。

## 輸出說明

  • 對於每個輸入的整數,必須依據下列格式輸出一行:
    X = Y (fib)
    其中 X 是原本的十進位數字,Y 是其費氏進位表示法。

## 範例測試

  • 輸入:

                 10
                 1
                 2
                 3
                 4
                 5
                 6
                 7
                 8
                 9
                 10
  • 輸出:

                 1 = 1 (fib)
                 2 = 10 (fib)
                 3 = 100 (fib)
                 4 = 101 (fib)
                 5 = 1000 (fib)
                 6 = 1001 (fib)
                 7 = 1010 (fib)
                 8 = 10000 (fib)
                 9 = 10001 (fib)
                 10 = 10010 (fib)

## 解題思路

  • 建構費氏數列:
  1. 題目提及輸入的最大數值小於 100000000。
  2. 我們需要先建立一個費氏數列,直到數列最大值超過 100000000 為止。
  3. 注意:在費氏進位中,通常不使用費氏數列的前兩項(即 0 和第一個 1),而是從 [1, 2, 3, 5, 8, 13...] 開始計算。
  • 貪婪演算法 (Greedy Approach) 分解:
  1. 從費氏數列中「小於或等於 $N$」的最大項開始由大到小檢查。
  2. 如果目前的費氏數小於或等於 $N$,就將該位置記為 '1',並讓 $N$ 減去該費氏數。
  3. 如果小於 $N$ 且前面已經開始輸出 '1'(即非字首的 0),則記為 '0'。
  4. 持續檢查到數列結束。

## 實作程式碼 (Python)

import sys

# 建立一個足夠大的費氏參考數列
fib = [0, 1]
while fib[-1] < 100000000:
    fib.append(fib[-1] + fib[-2])

# 先讀取資料總筆數
for _ in range(int(sys.stdin.readline())):
    # 假設當前讀入資料數值為n
    n = int(sys.stdin.readline())
    print(n, "=", end=" ")
    found = False
    
    # 對費氏數列進行逆向搜尋,當搜尋到費氏數 i 小於等於 n,就進入印字模式輸出 1 並更新 n = n - i
    for i in fib[:1:-1]:
        if n >= i:
            print("1", end="")
            n -= i
            found = True
        # 在印字模式時,持續對費氏數列進行逆向搜尋。
	# 當費氏數 i 大於 n,則印字輸出 0,反之印字輸出 1 並更新 n = n - i
        elif found:
            print("0", end="")
            
    print(" (fib)")