熱門文章

2026年4月12日 星期日

ZeroJudge 解題筆記:d111. 10110 - Light, more light

作者:王一哲
日期:2026年4月12日


ZeroJudge 題目連結:d111. 10110 - Light, more light

解題想法


這題考數學。第 $1$ 趟及第 $n$ 趟都會改變第 $n$ 盞燈的開關,效果抵消。只要找第 $2$ 趟到第 $(n-1)$ 趟之間改變第 $n$ 盞燈的開關次數 $cnt$,也就是找因數的數量,如果 $cnt$ 是奇數輸出 $yes$,反之輸出 $no$。可以用 for 迴圈找 $i = 2$ 到 $i = \sqrt{n}$ 之間有幾個 $n$ 的因數,如果 $i^2 \neq n$ 增加 $2$ 個因數,如果 $i^2 = n$ 增加 $1$ 個因數。所以這題可以直接用 $n$ 是否為完全平方數找答案。

Python 程式碼


找 $n$ 的因數數量,使用時間約為 14 ms,記憶體約為 8.1 MB,通過測試。
import sys

result = []
for line in sys.stdin:
    n = int(line)
    if n == 0: break
    factors = {1, n}
    for i in range(2, int(n**0.5)+1):
        if n%i == 0:
            factors.add(i)
            factors.add(n//i)
    result.append("yes\n" if len(factors)%2 == 1 else "no\n")
sys.stdout.write("".join(result))

檢查 $n$ 是否為完全平方數,使用時間約為 7 ms,記憶體約為 8.2 MB,通過測試。
import sys

result = []
for line in sys.stdin:
    n = int(line)
    if n == 0: break
    r = int(n**0.5)
    result.append("yes\n" if r*r == n else "no\n")
sys.stdout.write("".join(result))


2026年4月11日 星期六

ZeroJudge 解題筆記:d096. 00913 - Joana and the Odd Numbers

作者:王一哲
日期:2026年4月11日


ZeroJudge 題目連結:d096. 00913 - Joana and the Odd Numbers

解題想法


這題考數學。假設最後一項有 $n$ 項,則題目問的是第 $i$ 列($i = \frac{n+1}{2}$),此列最後一項 $$ a = \frac{[1 + 1 + 2 \times(i-1)] \times i}{2} $$ 最後 $3$ 項相加為 $(2a -1) \times 3 - 6$。 這題的測資 $n$ 有點大,用 C++ 解題時用 int 會溢位。

Python 程式碼


使用時間約為 7 ms,記憶體約為 8.4 MB,通過測試。
import sys

result = []
for line in sys.stdin:
    n = int(line)  # 最後一列有 n 項
    i = (n+1) // 2  # 第 i 列
    a = (1 + 1 + 2*(i-1)) * i // 2  # 第 i 列的最後 1 項
    result.append(f"{(a*2 - 1)*3 - 6:d}\n")  # 第 i 列最後 3 項相加
sys.stdout.write("".join(result))


2026年4月10日 星期五

ZeroJudge 解題筆記:d095. 00579 - ClockHands

作者:王一哲
日期:2026年4月10日


ZeroJudge 題目連結:d095. 00579 - ClockHands

解題想法


先計算時針與12點的夾角,由於時針每12小時轉360度,1小時轉30度,1分鐘轉0.5度,計算角度前先將小時 h 對 12 取餘數。再計算分針與12點的夾角,分針每60分鐘轉360時,1分鐘轉6度。再將兩個角度相減、取絕對值,如果絕對值大於180度,再將答案改成360度減掉絕對值。

Python 程式碼


使用時間約為 7 ms,記憶體約為 8.2 MB,通過測試。
import sys

result = []
for line in sys.stdin:
    if line.strip() == "0:00": break
    h, m = map(int, line.split(":"))
    a = h%12*30 + m/2
    b = m*6
    c = abs(a-b)
    if c > 180: c = 360 - c
    result.append(f"{c:.3f}\n")
sys.stdout.write("".join(result))


2026年4月9日 星期四

ZeroJudge 解題筆記:d094. 00478 - Points in Figures: Rectangles and Circles, and Triangles

作者:王一哲
日期:2026年4月9日


ZeroJudge 題目連結:d094. 00478 - Points in Figures: Rectangles and Circles, and Triangles

解題想法


這題是 d093. 00477 - Points in Figures: Rectangles and Circles 的加強版,除了長方形、圓形之外還多了三角形,可以用上一題的程式碼為基礎,再加上檢查點是否在三角形內的自訂函式。 假設平面上的三角形 ABC 頂點座標分別為 $(x_A, y_A), (x_B, y_B), (x_C, y_C)$,如果要判斷平面上的點 P $(x_P, y_P)$ 是否在三角形之中,P 點座標可以表示為 $$ P = uA + vB + wC $$ 解聯立後可得 $$ \begin{align*} u &= \frac{(x_B - x_A)(y_P - y_A) - (y_B - y_A)(x_P - x_A)}{(x_B - x_A)(y_C - y_A) - (y_B - y_A)(x_C - x_A)}\\ v &= \frac{(x_C - x_A)(y_P - y_A) - (y_C - y_A)(x_P - x_A)}{(x_C - x_A)(y_B - y_A) - (y_C - y_A)(x_B - x_A)} \\ w &= 1 - u - v & \end{align*} $$ 係數符合的條件為
  • $0 \leq u \leq 1$
  • $0 \leq v \leq 1$
  • $0 \leq w \leq 1$


Python 程式碼


使用時間約為 9 ms,記憶體約為 8.6 MB,通過測試。
import sys

def in_rectangle(x, y, x1, y1, x2, y2):
    """
    代入要測試的點座標 (x, y)
    長方形左上頂點座標 (x1, y1)
    長方形右下頂點座標 (x2, y2)
    判斷點是否在長方形內
    """
    if x1 < x < x2 and y1 > y > y2: return True
    return False

def in_circle(x, y, xc, yc, r):
    """
    代入要測試的點座標 (x, y)
    圓心座標 (xc, yc)、半徑 r
    判斷點是否在圓形內
    """
    if (x-xc)**2 + (y-yc)**2 < r**2: return True
    return False

def in_triangle(x, y, xA, yA, xB, yB, xC, yC):
    """
    代入要測試的點座標 (x, y)
    三角形頂點座標 (xA, yA), (xB, yB), (xC, yC)
    判斷點是否在三角形內
    """
    u = ((xB - xA)*(y - yA) - (yB - yA)*(x - xA)) / ((xB - xA)*(yC - yA) - (yB - yA)*(xC - xA))
    v = ((xC - xA)*(y - yA) - (yC - yA)*(x - xA)) / ((xC - xA)*(yB - yA) - (yC - yA)*(xB - xA))
    w = 1 - u - v
    if 0 <= u <= 1 and 0 <= v <= 1 and 0 <= w <= 1: return True
    return False

""" 讀取形狀 """
figures = [[]]
for line in sys.stdin:
    if line.strip() == "*": break
    data = line.split()
    if data[0] == "r":
        figures.append([0] + list(map(float, data[1:])))
    elif data[0] == "c":
        figures.append([1] + list(map(float, data[1:])))
    else:
        figures.append([2] + list(map(float, data[1:])))

""" 判斷點是否在圖形內並組合答案 """
result = []
i = 0
n = len(figures)
for line in sys.stdin:
    if line.strip() == "9999.9 9999.9": break
    i += 1
    state = False
    x, y = map(float, line.split())
    for j in range(1, n):
        if figures[j][0] == 0:
            if in_rectangle(x, y, figures[j][1], figures[j][2], figures[j][3], figures[j][4]):
                result.append(f"Point {i:d} is contained in figure {j:d}\n")
                state = True
        elif figures[j][0] == 1:
            if in_circle(x, y, figures[j][1], figures[j][2], figures[j][3]):
                result.append(f"Point {i:d} is contained in figure {j:d}\n")
                state = True
        else:
            if in_triangle(x, y, figures[j][1], figures[j][2], figures[j][3], figures[j][4], figures[j][5], figures[j][6]):
                result.append(f"Point {i:d} is contained in figure {j:d}\n")
                state = True
    if not state:
        result.append(f"Point {i:d} is not contained in any figure\n")
sys.stdout.write("".join(result))


2026年4月8日 星期三

ZeroJudge 解題筆記:d093. 00477 - Points in Figures: Rectangles and Circles

作者:王一哲
日期:2026年4月8日


ZeroJudge 題目連結:d093. 00477 - Points in Figures: Rectangles and Circles

解題想法


另外寫兩個自訂函式 in_rectangle 及 in_circle,分別用來檢查指定的座標是否在長方形或圓形之中。先儲存所有形狀的資料,讀取到檢查的點之後,依序檢查這個點在哪些編號的圖形之中並輸出答案。

Python 程式碼


使用時間約為 9 ms,記憶體約為 8.5 MB,通過測試。
import sys

def in_rectangle(x, y, x1, y1, x2, y2):
    """
    代入要測試的點座標 (x, y)
    長方形左上頂點座標 (x1, y1)
    長方形右下頂點座標 (x2, y2)
    判斷點是否在長方形內
    """
    if x1 < x < x2 and y1 > y > y2: return True
    return False

def in_circle(x, y, xc, yc, r):
    """
    代入要測試的點座標 (x, y)
    圓心座標 (xc, yc)、半徑 r
    判斷點是否在圓形內
    """
    if (x-xc)**2 + (y-yc)**2 < r**2: return True
    return False

""" 讀取形狀 """
figures = [[]]
for line in sys.stdin:
    if line.strip() == "*": break
    data = line.split()
    if data[0] == "r":
        figures.append([0] + list(map(float, data[1:])))
    elif data[0] == "c":
        figures.append([1] + list(map(float, data[1:])))

""" 判斷點是否在圖形內並組合答案 """
result = []
i = 0
n = len(figures)
for line in sys.stdin:
    if line.strip() == "9999.9 9999.9": break
    i += 1
    state = False
    x, y = map(float, line.split())
    for j in range(1, n):
        if figures[j][0] == 0:
            if in_rectangle(x, y, figures[j][1], figures[j][2], figures[j][3], figures[j][4]):
                result.append(f"Point {i:d} is contained in figure {j:d}\n")
                state = True
        elif figures[j][0] == 1:
            if in_circle(x, y, figures[j][1], figures[j][2], figures[j][3]):
                result.append(f"Point {i:d} is contained in figure {j:d}\n")
                state = True
    if not state:
        result.append(f"Point {i:d} is not contained in any figure\n")
sys.stdout.write("".join(result))


2026年4月7日 星期二

ZeroJudge 解題筆記:k652. 二元搜尋樹復原 (BST)

作者:王一哲
日期:2026年4月7日


ZeroJudge 題目連結:k652. 二元搜尋樹復原 (BST)
原題連結

解題想法


自訂類別 Node 及 BST,在 BST 之中用遞迴從根節點開始往下找插入新節點的位置,題目的輸出方式是中序走訪,但是同時要印出父節點的值。詳細的說明已經寫在程式碼的註解之中。

Python 程式碼


使用時間約為 0.6 s,記憶體約為 61.8 MB,通過測試。
class Node:
    def __init__(self, val):
        self.val = val
        self.left = None
        self.right = None

class BST:
    def __init__(self):
        self.root = None

    def insert(self, val, node = None):
        # 如果 node 為 None,設為 self.root,如果 self.root 是空的,新增 self.root
        if node is None:
            node = self.root
            if self.root is None:
                self.root = Node(val)
                return
        # 如果 val 較小,放到 node.left,如果 node.left 是空的,新增 node.left
        # 如果已經有 node.left,遞迴,試著放到 node.left 之下
        if val < node.val:
            if not node.left:
                node.left = Node(val)
            else:
                self.insert(val, node.left)
        # 如果 val 較大,放到 node.right,如果 node.right 是空的,新增 node.right
        # 如果已經有 node.right,遞迴,試著放到 node.right 之下
        else:
            if not node.right:
                node.right = Node(val)
            else:
                self.insert(val, node.right)

    def inorder(self, node = None, parent = -1):
        # 中序走訪,順序為 left, root, right
        # 如果 node 為 None,設為 self.root
        if node is None:
            node = self.root
        # 如果有 node.left,遞迴,代入 node.left 及 node.val
        if node.left:
            self.inorder(node.left, node.val)
        # 印出 node.val 及父節點的值;如果是根節點,parent 的值為 -1
        print(node.val, parent)
        # 如果有 node.right,遞迴,代入 node.right 及 node.val
        if node.right:
            self.inorder(node.right, node.val)

def solve():
    import sys

    result = []
    data = sys.stdin.read().split()
    ptr = 0
    while ptr < len(data):
        n = int(data[ptr])
        ptr += 1
        # 測資給的順序是後序走訪,最後一個是根節點,倒序加入 bst 之中
        arr = list(map(int, data[ptr:ptr+n]))
        ptr += n
        bst = BST()
        for u in arr[::-1]:
            bst.insert(u)
        bst.inorder()

if __name__ == "__main__":
    solve()


2026年4月6日 星期一

ZeroJudge 解題筆記:d088. 00127 - "Accordian" Patience

作者:王一哲
日期:2026年4月6日


ZeroJudge 題目連結:d088. 00127 - "Accordian" Patience

解題想法


這題的測資格式比較特別,如果用 Python 解題,最好用 sys.stdin.read().split() 一次讀取所有的測資,再用索引值分割,這樣會比較方便。儲存所有牌堆資料時,用可以改變長度、刪除資料的 list 及 vector 比較方便。用 while 迴圈檢查是否有可以合併的牌堆,直到沒有可以合併的牌堆為止。

Python 程式碼


使用時間約為 10 ms,記憶體約為 8.6 MB,通過測試。
def solve():
    import sys

    result = []
    data = sys.stdin.read().split()
    ptr = 0
    while ptr < len(data):
        # 停止條件
        if data[ptr] == "#": break
        # 讀取兩行測資組成牌堆
        piles = [[card] for card in data[ptr:ptr+52]]
        ptr += 52
        # 移動過程
        changed = True  # 是否有交換牌
        while changed:  # 如果有交換繼續執行
            changed = False  # 先設定為 False
            i = 1  # 從牌堆 1 開始檢查
            while i < len(piles):  # 如果 i 還沒有出界繼續執行
                # 先檢查牌堆 i 與 i-3 最上面的牌是否同花色或同數字
                if i >= 3 and (piles[i][-1][0] == piles[i-3][-1][0] or \
                               piles[i][-1][1] == piles[i-3][-1][1]):
                    piles[i-3].append(piles[i].pop())  # 牌堆 i 最上面移到牌堆 i-3
                    if not piles[i]: del piles[i]  # 如果牌堆 i 是空的,刪除
                    changed = True  # 有交換牌
                    break  # 中止迴圈
                # 檢查牌堆 i 與 i-1 最上面的牌是否同花色或同數字
                elif i >= 1 and (piles[i][-1][0] == piles[i-1][-1][0] or \
                                 piles[i][-1][1] == piles[i-1][-1][1]):
                    piles[i-1].append(piles[i].pop())  # 牌堆 i 最上面移到牌堆 i-1
                    if not piles[i]: del piles[i]  # 如果牌堆 i 是空的,刪除
                    changed = True  # 有交換牌
                    break  # 中止迴圈
                # 牌堆 i 不能交換,檢查下一張
                i += 1
        # 組合答案
        n = len(piles)  # 牌堆數量
        nums = [len(pile) for pile in piles]  # 各牌堆張數
        if n == 1:  # 只有一堆
            result.append(f"1 pile remaining: {nums[0]:d}\n")
        else:  # 不只一堆
            result.append(f"{n:d} piles remaining: " + " ".join(map(str, nums)) + "\n")
    # 輸出答案
    sys.stdout.write("".join(result))

if __name__ == "__main__":
    solve()


2026年4月5日 星期日

ZeroJudge 解題筆記:d087. 00107 - The Cat in the Hat

作者:王一哲
日期:2026年4月5日


ZeroJudge 題目連結:d087. 00107 - The Cat in the Hat

解題想法


寫一個自訂函式 check,輸入起始高度 h、倍率 n,計算總數、高度為 1 的數量、總高度。用 for 迴圈依序測試倍率 1 ~ H-1,用 check 檢查代入的倍率高度為1的貓的數目是否符合測資。

Python 程式碼


使用時間約為 7 ms,記憶體約為 8.4 MB,通過測試。
import sys

def check(h, n):  # 輸入起始高度、倍率
    total, curr, hsum = 1, 1, h  # 總數,目前的數量,總高度
    h //= 1+n  # 新的高度
    while h >= 1:  # 如果 h 大於等於 1 繼續執行
        curr *= n  # 更新數量為 n 倍
        total += curr  # 更新總數
        hsum += curr*h  # 更新總高度
        h //= 1+n  # 更新高度
    return total, curr, hsum  # 回傳總數、高度為 1 的數量、總高度
    
result = []
lines = sys.stdin.readlines()
for line in lines:
    H, M = map(int, line.split())  # 起始高度、高度為 1 的數量
    if H == 0 and M == 0: break  # 如果 H 與 M 皆等於 0,中止迴圈
    total, imin, hsum = H, 1, H  # 試算結果,總數、高度為 1 的數量、總高度
    for i in range(1, H):  # 測試倍率 1 ~ H-1
        total, imin, hsum = check(H, i)  # 回傳測試結果
        if imin == M: break  # 如果 imin 等於 M,找到答案,中止迴圈
    result.append(f"{total-imin:d} {hsum:d}\n")
sys.stdout.write("".join(result))

2026年4月4日 星期六

ZeroJudge 解題筆記:d057. 11494 - Queen

作者:王一哲
日期:2026年4月4日


ZeroJudge 題目連結:d057. 11494 - Queen

解題想法


可能的步數有 3 種:
  1. 不需要移動,回傳 0。
  2. 在同一列、同一欄、斜直線上,回傳 1。
  3. 其它狀況,走 2 步一定會走到,回傳 2。


Python 程式碼


使用時間約為 10 ms,記憶體約為 3.6 MB,通過測試。
import sys

def find_step(x1, y1, x2, y2):  # 輸入起點、終點找步數
    if x1 == x2 and y1 == y2: return 0  # 不需要移動,回傳 0
    if x1 == x2 or y1 == y2: return 1  # 在同一欄或列,回傳 1
    if x2 - x1 == y2 - y1:  # 右上、左下斜直線
        dx = x2 - x1  # x 方向位移
        dy = y2 - y1  # y 方向位移
        if 1 <= x1 + dy <= 8 and 1 <= y1 + dx <= 8:  # 沒出界
            return 1  # 回傳 1
    if x2 - x1 == y1 - y2:  # 左上、右下斜直線
        dx = x2 - x1  # x 方向位移
        dy = y2 - y1  # y 方向位移
        if 1 <= x1 - dy <= 8 and 1 <= y1 - dx <= 8:  # 沒出界
            return 1  # 回傳 1
    # 其它狀況,最多只要水平、垂直各走一步,回傳 2
    return 2

result = []
lines = sys.stdin.readlines()
for line in lines:
    if line.strip() == "0 0 0 0": break
    step = find_step(*map(int, line.split()))
    result.append(f"{step:d}\n")
sys.stdout.write("".join(result))


2026年4月3日 星期五

ZeroJudge 解題筆記:d056. 10013 - Super long sums

作者:王一哲
日期:2026年4月3日


ZeroJudge 題目連結:d056. 10013 - Super long sums

解題想法


這題如果用 Python 解題,最好用串列 a、b 儲存超長整數,再用另一個串列 ans 儲存計算結,串列資料格式為字串。運算時由 a、b 最後面住前讀取資料,將計算結果用字串格式存入 ans,輸出答案時將 ans 反序輸出。如果用 C++ 解題,可以用字串或 vector 儲存超長整數,用字串格式好像速度快一點。

Python 程式碼


使用時間約為 0.4 s,記憶體約為 141.1 MB,通過測試。
def solve():
    import sys

    result = []
    data = sys.stdin.read().split()
    ptr = 0
    while ptr < len(data):
        n = int(data[ptr])
        ptr += 1
        for _ in range(n):
            m = int(data[ptr])
            ptr += 1
            a, b = [], []
            for _ in range(m):
                c, d = map(int, data[ptr:ptr+2])
                ptr += 2
                a.append(c)
                b.append(d)
            ans = []
            carry = 0
            for i, j in zip(a[::-1], b[::-1]):
                k = int(i) + int(j) + carry
                if k >= 10:
                    carry = 1
                    ans.append(f"{k%10}")
                else:
                    carry = 0
                    ans.append(f"{k}")
            if carry == 1: ans.append("1")
            res = "".join(ans[::-1])
            if not result: result.append(f"{res}\n")
            else: result.append(f"\n{res}\n")
        sys.stdout.write("".join(result))

if __name__ == "__main__":
    solve()


2026年4月2日 星期四

ZeroJudge 解題筆記:d055. 11437 - Triangle Fun

作者:王一哲
日期:2026年4月2日


ZeroJudge 題目連結:d055. 11437 - Triangle Fun

解題想法


數學題。分點公式是高中數學課程內容,假設要在兩個點 $A$、$B$ 找到另一個點 $C$,使 $\overline{AB} : \overline{CB} = m : n$,則 $$ C = \frac{n}{m+n} A + \frac{m}{m+n} B $$ 題目的提示中有由寫三角形三個頂點求面積的公式 $$ area = \frac{1}{2} \times \left | A_x B_y - A_y B_x + B_x C_y - C_x B_y + C_x A_y - C_y A_x \right | $$ 還要自己推導,由兩條不平行的直線端點求交點座標的公式。假設 $(x_1, y_1), (x_2, y_2)$ 是直線 $y = ax + b$ 的端點座標,$(x_3, y_3), (x_4, y_4)$ 是直線 $y = a'x + b'$ 的端點座標。將 $(x_1, y_1), (x_2, y_2)$ 代入 $y = ax + b$ 可得 $$ y_1 = ax_1 + b ~~~~~ y_2 = ax_2 + b $$ 利用加減消去法可得 $$ a = \frac{y_1 - y_2}{x_1 - x_2} ~~~~~ b = \frac{x_2 y_1 - x_1 y_2}{x_2 - x_1} $$ 同理可得 $$ a' = \frac{y_3 - y_4}{x_3 - x_4} ~~~~~ b' = \frac{x_4 y_3 - x_3 y_4}{x_4 - x_3} $$ 再求 $y = ax + b$ 與 $y = a'x + b'$ 的交點 $(x, y)$,利用加減消去法可得 $$ x = \frac{b' - b}{a - a'} ~~~~~ y = \frac{a'b - ab'}{a' - a} $$

Python 程式碼


使用時間約為 11 ms,記憶體約為 3 MB,通過測試。
def find_point(ax, ay, bx, by):
    """
    代入直線的兩個端點 A、B
    求與 A、B 距離比為 1:2 的座標
    """
    cx = (2*ax + bx) / 3
    cy = (2*ay + by) / 3
    return cx, cy

def intersection(x1, y1, x2, y2, x3, y3, x4, y4):
    """
    x1, y1, x2, y2 是一條直線 y = ax + b 的兩個端點
    x3, y3, x4, y5 是另一條直線 y = a'x + b' 的兩個端點
    兩條直線不平行,求直線的交點
    """
    a = (y1 - y2) / (x1 - x2)
    b = (x2*y1 - x1*y2) / (x2 - x1)
    ap = (y3 - y4) / (x3 - x4)
    bp = (x4*y3 - x3*y4) / (x4 - x3)
    x = (bp - b) / (a - ap)
    y = (ap*b - a*bp) / (ap - a)
    return x, y

def find_area(ax, ay, bx, by, cx, cy):
    """
    代入三角形 ABC 的三的頂點座標求面積
    """
    return abs(ax*by - ay*bx + bx*cy - by*cx + cx*ay - cy*ax)/2

t = int(input())
for _ in range(t):
    Ax, Ay, Bx, By, Cx, Cy = map(float, input().split())
    Dx, Dy = find_point(Bx, By, Cx, Cy)
    Ex, Ey = find_point(Cx, Cy, Ax, Ay)
    Fx, Fy = find_point(Ax, Ay, Bx, By)
    Px, Py = intersection(Bx, By, Ex, Ey, Ax, Ay, Dx, Dy)
    Qx, Qy = intersection(Bx, By, Ex, Ey, Cx, Cy, Fx, Fy)
    Rx, Ry = intersection(Ax, Ay, Dx, Dy, Cx, Cy, Fx, Fy)
    area = find_area(Px, Py, Qx, Qy, Rx, Ry)
    print(f"{round(area):d}")


2026年4月1日 星期三

ZeroJudge 解題筆記:d054. 11310 - DELIVERY DEBACLE

作者:王一哲
日期:2026年4月1日


ZeroJudge 題目連結:d054. 11310 - DELIVERY DEBACLE

解題想法


這題考動態規畫,最難的地方是找遞迴關係式。$dp[n]$ 代表長度為 $n$ 時的排列方式數量,先列出前幾項:
  1. $dp[0] = 1$,沒有任何的蛋糕,只有 1 種方式。
  2. $dp[1] = 1$,只能放 2 個方形蛋糕,只有 1 種方式。
  3. $dp[2] = 4 + 1 = 5$,用 1 個 L 形蛋糕及 1 個方形蛋糕佔據其中 4 格,再用 2 個方形蛋糕佔據剩下的 2 格,因為 L 形蛋糕的組合可以旋轉,有 4 種方式;也可以用 6 個方形蛋糕填滿 6 格,1 種方式;加起來共有 5 種方式。
$n = 3$ 開始會出現另一種排列方式,用 2 個 L 形蛋糕佔據其中 6 格,有以下 2 種方式
OOX     OXX
OXX     OOX
因此 $dp[3]$ 的組成有:
  1. $dp[2] = 5$,用 2 個方形蛋糕佔據最後 2 格。
  2. $4 \times dp[1] = 4 \times 1 = 4$,用 1 個 L 形蛋糕及 1 個方形蛋糕佔據最後 4 格,因為這個組合可以旋轉,要乘以 4。
  3. $2 \times dp[0] = 2 \times 1 = 2$,用 2 個 L 形蛋糕佔據最後 6 格,這個組合有 2 種,要乘以 2。
$dp[3] = dp[2] + 4 \times dp[1] + 2 \times dp[0] = 5 + 4 \times 1 + 2 \times 1 = 11$ $dp[4]$ 的組成有:
  1. $dp[3] = 11$,用 2 個方形蛋糕佔據最後 2 格。
  2. $4 \times dp[2] = 4 \times 5 = 4$,用 1 個 L 形蛋糕及 1 個方形蛋糕佔據最後 4 格,因為這個組合可以旋轉,要乘以 4。
  3. $2 \times dp[1] = 2 \times 1 = 2$,用 2 個 L 形蛋糕佔據最後 6 格,這個組合有 2 種,要乘以 2。
  4. $dp[4] = dp[3] + 4 \times dp[2] + 2 \times dp[1] = 11 + 4 \times 5 + 2 \times 1 = 35$
遞迴關係式為 $$ dp[n] = dp[n-1] + 4 \times dp[n-2] + 2 \times dp[n-3] $$

Python 程式碼


使用時間約為 7 ms,記憶體約為 2.8 MB,通過測試。
def solve():
    import sys

    # 建表,n = 0 ~ 40
    dp = [1]*41
    dp[2] = 5
    for i in range(3, 41):
        dp[i] = dp[i-1] + 4*dp[i-2] + 2*dp[i-3]
    # 讀取資料並查表找答案
    result = []
    data = sys.stdin.read().split()
    ptr = 1
    while ptr < len(data):
        n = int(data[ptr])
        ptr += 1
        result.append(f"{dp[n]:d}\n")
    sys.stdout.write("".join(result))

if __name__ == "__main__":
    solve()


2026年3月31日 星期二

ZeroJudge 解題筆記:d048. 11309 - Counting Chaos

作者:王一哲
日期:2026年3月31日


ZeroJudge 題目連結:d048. 11309 - Counting Chaos

解題想法


寫一個自訂函式 check,檢查輸入的時間 h:m 是否為迴文。再寫另一個自訂函式 solve,輸入讀取的到字串 s,用 : 分割為 h:m,再換算成分鐘,由於答案範圍很小,用線性搜尋從目前的時間 curr 開始找答案即可。

Python 程式碼


使用時間約為 33 ms,記憶體約為 2.9 MB,通過測試。
def check(h, m):  # 檢查 h:m 是否為迴文
    time = f"{h:02d}{m:02d}"  # 補 0 組成字串
    time = time.lstrip('0')  # 刪除前置 0
    return time == time[::-1]  # 是否為迴文

def solve(s):  # 輸入字串求解
    h, m = map(int, s.split(':'))  # 用 : 分割字串轉成整數
    curr = h*60 + m  # 現在的時刻,以分鐘為單位
    for _ in range(1, 1440):  # 線性搜尋找答案
        curr += 1  # 時間加 1 分鐘
        curr_h = curr//60%24  # 時
        curr_m = curr%60  # 分
        if check(curr_h, curr_m):  # 如果是迴文,回傳答案
            return f"{curr_h:02d}:{curr_m:02d}"
    return "00:00"  # 預設的回傳值,理論上用不到

n = int(input())
for _ in range(n):
    print(solve(input()))


2026年3月30日 星期一

ZeroJudge 解題筆記:d045. 11222 - Only I did it!

作者:王一哲
日期:2026年3月30日


ZeroJudge 題目連結:d045. 11222 - Only I did it!

解題想法


我是用集合處理這題。先將三人解的題號分別存入集合 $a, b, c$,只有第一個人解出來的題目就是 $a$ 對 $b, c$ 的聯集取差集,只有第二個人解出來的題目就是 $b$ 對 $c, a$ 的聯集取差集,只有第三個人解出來的題目就是 $c$ 對 $a, b$ 的聯集取差集。分別計算三個差集的長度,輸出最長的差集對應的朋友編號、差集長度與題號。

Python 程式碼


使用時間約為 9 ms,記憶體約為 3.5 MB,通過測試。
T = int(input())
for t in range(1, T+1):
    print(f"Case #{t:d}:")
    a = set(tuple(map(int, input().split()))[1:])
    b = set(tuple(map(int, input().split()))[1:])
    c = set(tuple(map(int, input().split()))[1:])
    a_bc = a.difference(b.union(c))
    b_ca = b.difference(c.union(a))
    c_ab = c.difference(a.union(b))
    m, n, p = len(a_bc), len(b_ca), len(c_ab)
    imax = max(m, n, p)
    if m == imax: print(1, m, *sorted(a_bc))
    if n == imax: print(2, n, *sorted(b_ca))
    if p == imax: print(3, p, *sorted(c_ab))


2026年3月29日 星期日

ZeroJudge 解題筆記:d041. 11219 - How old are you?

作者:王一哲
日期:2026年3月29日


ZeroJudge 題目連結:d041. 11219 - How old are you?

解題想法


用自訂函式計算年齡,如果今天的日、月、年分別為 $dt, mt, yt$,生日的日、月、年分別為 $db, mb, yb$,先假設年齡 $age = yt - yb$,接下來再依照 $(mt, dt), (mb, db)$ 修正 $age$ 的值,如果 $mt < mb$ 或是 $mt = mb, dt < db$,則 $age$ 減 1。

Python 程式碼


使用時間約為 7 ms,記憶體約為 2.8 MB,通過測試。
def cal_age(today, birthday):
    dt, mt, yt = map(int, today.split('/'))
    db, mb, yb = map(int, birthday.split('/'))
    age = yt - yb
    #if (mt, dt) < (mb, db): age -= 1  # 第5、6行效果相同
    if mt < mb or (mt == mb and dt < db): age -= 1
    if age < 0: return "Invalid birth date"
    if age > 130: return "Check birth date"
    return str(age)

T = int(input())
for t in range(1, T+1):
    _ = input()
    ans = cal_age(input(), input())
    print(f"Case #{t:d}: {ans:s}")


2026年3月28日 星期六

ZeroJudge 解題筆記:c203. 13185 - DPA Numbers I

作者:王一哲
日期:2026年3月28日


ZeroJudge 題目連結:c203. 13185 - DPA Numbers I

解題想法


這題的測資很小,硬算就好。寫一個自訂函式 test 找 n 的所有因數和 $isum$,$isum$ 至少為 1,接下來從 $i = 2$ 開始往上測試到 $i = \sqrt{n}$ 為止,如果 $i$ 可以整除 $n$,則 $isum$ 加上 $i + (n/i)$;如果 $i^2 = n$,則 $isum$ 要再減去 $i$。最後比較 $isum$ 與 $n$ 的關係,輸出對應的答案。

Python 程式碼


使用時間約為 7 ms,記憶體約為 2.9 MB,通過測試。
def test(n):
    isum = 1
    for i in range(2, int(n**0.5) + 1):
        if n%i == 0:
            isum += i + n//i
        if i*i == n:
            isum -= i
    if isum == n: return "perfect"
    if isum < n: return "deficient"
    return "abundant"

def solve():
    import sys

    result = []
    data = sys.stdin.read().split()
    ptr = 1
    while ptr < len(data):
        result.append(f"{test(int(data[ptr]))}\n")
        ptr += 1
    sys.stdout.write("".join(result))

if __name__ == "__main__":
    solve()


2026年3月27日 星期五

ZeroJudge 解題筆記:c187. 10986 - Sending email

作者:王一哲
日期:2026年3月27日


ZeroJudge 題目連結:c187. 10986 - Sending email

解題想法


這題要求從起點 $S$ 到終點 $T$ 的最短距離,可以用戴克斯特拉演算法 (Dijkstra's algorithm) 解題。

Python 程式碼


使用時間約為 0.2 s,記憶體約為 20.7 MB,通過測試。
import sys, heapq

result = []  # 要輸出的結果
T = int(sys.stdin.readline())  # T 組測資
for t in range(1, T+1):  # 執行 T 次
    n, m, start, end = map(int, input().split())  # n 個節點,m 條邊,起點、終點
    if start == end:  # 特例,起點等於終點
        result.append(f"Case #{t:d}: 0\n")  # 答案 0
        for _ in range(m): _ = sys.stdin.readline()  # 依然要讀取以下 m 行測資
    else:  # 一般的狀況
        graph = dict()  # 節點連接關係
        for _ in range(m):  # 讀取 m 行測資
            u, v, w = map(int, sys.stdin.readline().split())  # 節點 u, v 相連,邊長 w
            if u not in graph: graph[u] = [(v, w)]
            else: graph[u].append((v, w))
            if v not in graph: graph[v] = [(u, w)]
            else: graph[v].append((u, w))
        cost = [float('inf')]*n  # 走到每個節點的最低成本
        pq = [(0, start)]  # 最小優先佇列,放入 (0, 起點)
        while pq and pq[0][1] != end:  # 如果 pq 還有資料而且最上面的資料不是終點
            curr, u = heapq.heappop(pq)  # 取出 pq 最上面的資料
            if u not in graph: continue  # 如果 u 沒有子節點,找下一個點
            for v, w in graph[u]:  # 依序讀取 u 的子節點及權重
                new_w = curr + w  # 新的成本
                if new_w < cost[v]:  # 如果比原來走到 v 的最低成本還低
                    cost[v] = new_w  # 更新 cost[v]
                    heapq.heappush(pq, (new_w, v))  # (new_w, v) 加入 pq
          # 如果 cost[end] 不等於預設值,可以走到終點
        ans = str(cost[end]) if cost[end] != float('inf') else "unreachable"
        result.append(f"Case #{t:d}: {ans:s}\n")
sys.stdout.write("".join(result))

2026年3月26日 星期四

ZeroJudge 解題筆記:c139. 00291 - The House Of Santa Claus

作者:王一哲
日期:2026年3月26日


ZeroJudge 題目連結:c139. 00291 - The House Of Santa Claus

解題想法


先用一個陣列儲存每個節點可以連接的節點編號,再寫一個自訂函式,用遞迴及回溯跑所有可能的畫法。

Python 程式碼


使用時間約為 7 ms,記憶體約為 2.9 MB,通過測試。
def solve():
    import sys
    ans = []
    graph = [[], [2, 3, 5], [1, 3, 5],
             [1, 2, 4, 5], [3, 5], [1, 2, 3, 4]]
    def backtrack(u, path, visited):
        if len(path) == 9:
            ans.append(path)
            return
        for v in graph[u]:
            if (u, v) in visited or (v, u) in visited: continue
            visited.add((u, v))
            visited.add((v, u))
            backtrack(v, path + [v], visited)
            visited.remove((u, v))
            visited.remove((v, u))
    backtrack(1, [1], set())
    result = ["".join(map(str, a)) + "\n" for a in ans]
    sys.stdout.write("".join(result))

if __name__ == "__main__":
    solve()


2026年3月25日 星期三

ZeroJudge 解題筆記:c134. 00668 - Parliament

作者:王一哲
日期:2026年3月25日


ZeroJudge 題目連結:c134. 00668 - Parliament

解題想法


這題要將某個正整數 n,分拆成任意個不重複的正整數,使分拆後的正整數乘積最大。因為拆成 1 不會使乘積變大,至少從 2 開始分拆,盡量拆成越多個數字越好。如果 $n \leq 4$ 分拆後的正整數乘積不會大於 $n$,因此題目的測資 $\geq 5$ 。分拆時從 $i=2$ 開始測試,每次分拆後將 $n-i$,直到 $n < i$ 為止,如果最後 $n > 0$ ,再將分拆出來最大的 $n$ 個數都加 1。 注意:此題的答案只要輸出分拆後的正整數,不需要輸出個數。題目敘述與測資的要求不合。

Python 程式碼


使用時間約為 7 ms,記憶體約為 2.9 MB,通過測試。
def solve():
    import sys
    
    result = []
    data = sys.stdin.read().split()
    T = int(data[0])
    ptr = 1
    while ptr < len(data):
        n = int(data[ptr])
        ptr += 1
        if result:  # 不是第一組測資,先換行
            result.append("\n")
        if n <= 4:  # 特例,小於等於 4,不能分拆
            result.append(f"{n:d}\n")
        else:
            i = 2  # 從 2 開始測試
            ans = []  # 分拆的數字
            while n >= i:  # 如果 n 還能分拆
                ans.append(i)  # i 加入 anss
                n -= i  # n 減去 i
                i += 1  # i 加 1
            for j in range(len(ans)-1, len(ans)-n-1, -1):  # 最後 n 個數加 1
                ans[j] += 1
            res = " ".join(map(str, ans))
            result.append(f"{res}\n")
    sys.stdout.write("".join(result))

if __name__ == "__main__":
    solve()


2026年3月24日 星期二

ZeroJudge 解題筆記:c133. 00639 - Don't Get Rooked

作者:王一哲
日期:2026年3月24日


ZeroJudge 題目連結:c133. 00639 - Don't Get Rooked

解題想法


這題很像八皇后,用集合 rooks 儲存已經放置城堡的位置,寫一個自訂函式 is_safe 檢查代入的位置 (row, col) 是否安全;再寫另一個自訂函式 backtrack 代入 (0, 0),用遞迴與回溯找答案。

Python 程式碼


使用時間約為 7 ms,記憶體約為 3 MB,通過測試。
import sys

def solve(n):
    grid = [list(sys.stdin.readline().strip()) for _ in range(n)]
    rooks = set()  # 已放置城堡的位置
    ans = 0  # 答案
    """ grid[row][col] 處是否可以放置城堡 """
    def is_safe(row, col):
        for r in range(row-1, -1, -1):  # 向上檢查
            if grid[r][col] == 'X': break
            if (r, col) in rooks: return False
        for r in range(row+1, n, 1):  # 向下檢查
            if grid[r][col] == 'X': break
            if (r, col) in rooks: return False
        for c in range(col-1, -1, -1):  # 向左檢查
            if grid[row][c] == 'X': break
            if (row, c) in rooks: return False
        for c in range(col+1, n, 1):  # 向右檢查
            if grid[row][c] == 'X': break
            if (row, c) in rooks: return False
        return True  # 通過 4 個方向的檢查,回傳 True
    """ 回溯,代入 row, col """
    def backtrack(row, col):
        nonlocal ans  # 這樣才能修改函數外的變數值
        if col == n:  # 如果欄已經出界
            col = 0   # 歸零
            row += 1  # 找下一列
        if row == n:  # 如果列已經出界
            ans = max(ans, len(rooks))  # 更新答案
            return    # 遞迴出口
        # 測試 (row, col) 是否可以放置城堡
        if grid[row][col] == '.' and is_safe(row, col):
            rooks.add((row, col))  # 放置城堡
            backtrack(row, col+1)  # 遞迴,測試下一欄是否能放置城堡
            rooks.remove((row, col))  # 回溯
        backtrack(row, col+1)  # 遞迴,不在 (row, col) 處放置城堡
    """ End of backtrack """
    backtrack(0, 0)  # 從 (0, 0) 開始測試
    return ans

result = []
for line in sys.stdin:
    if not line.strip(): continue
    n = int(line)
    if n == 0: break
    result.append(f"{solve(n):d}\n")
sys.stdout.write("".join(result))


2026年3月23日 星期一

ZeroJudge 解題筆記:c131. 00615 - Is It A Tree?

作者:王一哲
日期:2026年3月23日


ZeroJudge 題目連結:c131. 00615 - Is It A Tree?

解題想法


用自訂函式 test 代入節點資料,檢測是否為樹。檢測項目為
  • 讀取邊的端點 $u, v$,因為 $u$ 可以連到 $v$,$u$ 為父節點,$v$ 為子節點。如果 $v$ 已經有父節點,因為一個節點不可能有兩個父節點,不是樹,回傳 False。
  • 檢查是否只有一個根節點,因為不可能有兩個根節點。
  • 用 BFS 從根節點開始走訪,如果能夠走到所有節點,是樹,回傳 True。


Python 程式碼


使用時間約為 8 ms,記憶體約為 3.3 MB,通過測試。
import sys
from collections import deque

def test(data):  # 檢測用的函式,輸入節點資料
    if data == [0, 0]: return True  # 特例,資料只有 0, 0,回傳 True
    parent, children = dict(), dict()  # {v: u} v 的父節點是 u,u 的子節點集合
    nodes = set()  # 所有節點的集合
    for i in range(0, len(data), 2):  # 一次讀取兩筆測資
        u, v = data[i], data[i+1]  # 節點 u 連到 v
        nodes.add(u); nodes.add(v)  # u, v 加入 nodes
        if v in parent: return False  # 如果 v 已經有父節點,回傳 True
        parent[v] = u  # v 的父節點是 u
        if u not in children: children[u] = {v}  # 如果 u 還沒有子節點,建立集合,加入 v
        else: children[u].add(v)  # 反之,直接加入 v
    ### 檢查是否只有一個根節點 ###
    root = -1  # 根節點
    for node in nodes:  # 依序讀取所有節點
        if node not in parent:  # 如果 node 沒有父節點
            if root == -1: root = node  # 如果 root 等於 -1,node 是根節點
            elif root != -1: return False  # 反之,不可能有兩個根節點,回傳 False
    ### BFS 檢查所有節點是否相連 ###
    que = deque([root])  # 待走訪佇列,先加入根節點
    visited = {node: False for node in nodes}  # 各節點走訪狀態
    visited[root] = True  # 已走訪根㿝3點
    while que:  # 如果 que 還有資料繼續執行
        u = que.popleft()  # 從 que 開頭取出資料
        if u not in children: continue  # 如果 u 沒有子節點,找下一個點
        for v in children[u]:  # 依序讀取 u 的子節點 v
            if visited[v]: return False  # 如果 v 已走訪,環狀結構,回傳 False
            visited[v] = True  # 已走訪 v
            que.append(v)  # v 加入 que
    ### End of BFS,如果所有節點已走訪回傳 True ###
    return all(visited.values())

"""" 讀取資料並解題 """
lines = sys.stdin.readlines()  # 讀取所有測資
idx = 0  # 從 lines 讀取資料的索引值
ca = 0  # 第幾組測資
result = []  # 要輸出的字串
while idx < len(lines):  # 如果 idx 還沒有出界繼續執行
    line = lines[idx]  # 讀取一行
    idx += 1
    line = line.strip()  # 跳過空行
    if not line: continue
    if line == "-1 -1": break  # 中止迴圈的條件
    ca += 1
    if line == "0 0":  # 特例,這組測資只有 0 0
        data = [0, 0]
    else:  # 其它狀況
        data = list(map(int, line.split()))  # 轉成整數串列
        if data[-1] == 0 and data[-2] == 0:  # 如果最後兩項都是 0
            data = data[:-2]  # 去掉最後兩項,結束這組測資
        else:  # 還有測資
            while True:  # 繼續讀取,直到結尾是 0 0 為止
                tmp = list(map(int, lines[idx].split()))
                idx += 1
                if tmp[-1] == 0 and tmp[-2] == 0:
                    data += tmp[:-2]
                    break
                else: data += tmp
    state = " " if test(data) else " not "  # 依照測試結果決定輸出的內容
    result.append(f"Case {ca:d} is{state:s}a tree.\n")
sys.stdout.write("".join(result))


2026年3月22日 星期日

ZeroJudge 解題筆記:c130. 00574 - Sum It Up

作者:王一哲
日期:2026年3月22日


ZeroJudge 題目連結:c130. 00574 - Sum It Up

解題想法


這題考自訂函式的遞迴與回溯,同時用 set 儲存已經測試的過組合。

Python 程式碼


使用時間約為 10 ms,記憶體約為 3.4 MB,通過測試。
import sys

def solve(line):  # 解題用的函式
    nums = list(map(int, line.split()))  # 讀取一行測資
    t, n = nums[0], nums[1]  # 前兩項是目標、可用數字數量
    nums = nums[2:]  # 後面是可用的數字
    tested = set()  # 已測試過的組合,要用 tuple
    result = [f"Sums of {t:d}:\n"]  # 答案開頭
    """ 回溯,代入已選數字、加總、起點索引值 """
    def backtrack(choice, total, start):
        if tuple(choice) in tested: return  # 已測試過,遞迴出口
        tested.add(tuple(choice))  # choice 轉成 tuple 加入 tested
        if total > t: return  # 加總大於目標,遞迴出口
        if total == t:  # 加總等於目標,新增一組解,遞迴出口
            result.append("+".join(map(str, choice)) + "\n")
            return
        for i in range(start, n):  # 依序測試 nums[start] ~ nums[n-1]
            backtrack(choice + [nums[i]], total + nums[i], i+1)  # 選擇 nums[i]
            backtrack(choice, total, i+1)  # 不選擇 nums[i]
    """ End of backtrack """
    backtrack([], 0, 0)  # 呼叫 backtrack,代入空串列、加總 0、起點 0
    if len(result) == 1: result.append("NONE\n")  # 如果 result 長度為 1,加入 NONE
    sys.stdout.write("".join(result))  # 輸出答案

if __name__ == "__main__":
    for line in sys.stdin:
        line = line.strip()
        if not line: continue
        if line == "0 0": break
        solve(line)


2026年3月21日 星期六

ZeroJudge 解題筆記:c129. 00572 - Oil Deposits

作者:王一哲
日期:2026年3月21日


ZeroJudge 題目連結:c129. 00572 - Oil Deposits

解題想法


這類連通區塊的題目用 BFS 就對了。但是這題的連通方式跟踩地雷一樣,有 8 個方向要檢查。

Python 程式碼


使用時間約為 13 ms,記憶體約為 3.4 MB,通過測試。
import sys
from collections import deque

for line in sys.stdin:
    if not line.strip(): continue
    m, n = map(int, line.split())  # 地圖尺寸 m*n
    if m == 0 and n == 0: break  # 中止迴圈
    grid = [list(sys.stdin.readline().strip()) + ['*'] for _ in range(m)]  # 地圖最右邊加 * 當作哨兵
    grid.append(['*']*(n+1))  # 地圖最下方加 * 當作哨兵
    cnt = 0  # 連通區域數量
    """ BFS """
    for i in range(m):  # 依序掃過地圖每一格,不包含哨兵
        for j in range(n):
            if grid[i][j] == '*': continue  # 不含石油,找下一格
            que = deque([(i, j)])  # 待走訪的佇列
            cnt += 1  # 數量加 1
            grid[i][j] = '*'  # 此格已走訪,設定為 *
            while que:  # 如果 que 有資料繼續執行
                r, c = que.popleft()  # 取出 que 最前面的資料
                # 注意,這題的連通方式跟踩地雷一樣,有 8 個方向要檢查
                for dr, dc in ((0, 1), (1, 1), (1, 0), (1, -1), (0, -1), (-1, -1), (-1, 0), (-1, 1)):
                    nr, nc = r+dr, c+dc  # 要檢查 (nr, nc)
                    if grid[nr][nc] == '*': continue  # 不含石油,找下一格
                    grid[nr][nc] = '*'  # 此格已走訪,設定為 *
                    que.append((nr, nc))  # (nr, nc) 加到 que
    """ End of BFS """
    sys.stdout.write(f"{cnt:d}\n")


2026年3月20日 星期五

ZeroJudge 解題筆記:c128. 00544 - Heavy Cargo

作者:王一哲
日期:2026年3月20日


ZeroJudge 題目連結:c128. 00544 - Heavy Cargo

解題想法


這題跟 c125. 00534 - Frogger。 依序讀取兩座城市的名稱及道路的承受重量上限,城市之間連接的關係存入字典 graph,key 為城市的名稱,value 為連接的城市名稱,格式為 list;道路資料存入字典 edge,key 為兩座城市 $u, v$ 組成的 tupel,value 為重量,因為是無向圖,$(u, v), (v, u)$ 都要存資料。 開一個陣列 $ans$,儲存到達第 $i$ 座城市的路徑可以承受的最大重量的最小值。接下來用一個最大優先佇列 $pq$,資料為 $(重量, 城市名稱)$,代表到第 $i$ 座城市的路徑上某一條路可以承受的最大重量,重量大的資料放上面。因為 Python 的 heapq 是最小優先佇列,為了把最大值放在上面,重量要加負號。如果 $pq$ 還有資料,而且 $pq$ 最上面的城市不等於終點,while 迴圈繼續執行。每次取出 $pq$ 最上方的資料,目前的重量 $curr$、城市的名稱 $u$,掃過與 $u$ 相連的城市 $v$,取 $curr$ 與 $edge[u, v]$ 較小者為新的重量 $new_w$,如果 $new_w < ans[v]$,更新 $ans[v]$,並將 ${new_w, v}$ 加入 $pq$。

Python 程式碼


使用時間約為 18 ms,記憶體約為 5.4 MB,通過測試。
import sys, heapq

ca = 0
result = []
for line in sys.stdin:
    if not line.strip(): continue
    n, r = map(int, line.split())  # n 個城市,r 條道路
    if n == 0 and r == 0: break  # 中止迴圈的條件
    ca += 1  # 測資編號加 1
    graph, edge = dict(), dict()  # 城市連接的關係,道路可以承受的重量
    for _ in range(r):  # 讀取 r 條道路
        u, v, w = sys.stdin.readline().split()  # 城市 u、v 之間的道路可以承受重量 w
        w = int(w)  # w 轉成 int
        if u not in graph: graph[u] = [v]  # 如果 u 不在 graph 之中,設定為 [v]
        else: graph[u].append(v)  # 反之,加上 v
        if v not in graph: graph[v] = [u]  # 如果 v 不在 graph 之中,設定為 [u]
        else: graph[v].append(u)  # 反之,加上 u
        edge[u, v] = w  # u、v 之間的道路可以承受重量 w
        edge[v, u] = w
    ans = {k: 0 for k in graph.keys()}  # 走到每個城市的路上可以承受的重量最大值
    start, end = sys.stdin.readline().split()  # 起點、終點
    pq = [(-float('inf'), start)]  # (-重量, 城市),加負號讓重量大的放上面
    while pq and pq[0][1] != end:  # 如果 pq 有資料而且最上面的城市不是終點
        w, u = heapq.heappop(pq)  # 取出 pq 最上面的重量、城市
        w = -w  # w 轉成正值
        for v in graph[u]:  # 依序取出 u 連接的城市 v
            new_w = min(w, edge[u, v])  # 取 w 與 u、v 之間可以承受重量較小者
            if new_w > ans[v]:  # 如果 new_w 是新的最大值
                ans[v] = new_w  # 更新 ans[v]
                heapq.heappush(pq, (-new_w, v))  # (-new_w, v) 加入 pq
    if ca > 1: result.append("\n")  # 如果不是第 1 筆測資,先換行
    result.append(f"Scenario #{ca:d}\n{ans[end]:d} tons\n")  # 答案加入 result
sys.stdout.write("".join(result))


2026年3月19日 星期四

ZeroJudge 解題筆記:c127. 00537 - Artificial Intelligence

作者:王一哲
日期:2026年3月19日


ZeroJudge 題目連結:c127. 00537 - Artificial Intelligence

解題想法


我用字典儲存前綴詞及單位對應的倍率,從讀取到的字串之中搜尋 I=、U=、P= 這 3 個字串,如果有找到這 3 個字串,再從字串索引值加 2 的位置開始向後讀取字元,找出對應的數字。最後配合前綴詞及單位調整數值的大小,輸出答案。

Python 程式碼


用時間約為 7 ms,記憶體約為 3 MB,通過測試。
prefix = {'m': 0.001, 'k': 1000, 'M': 1000000, 'A': 1, 'V': 1, 'W': 1}  # 前綴詞及單位對應的倍率
T = int(input())  # T 組測資
for t in range(1, T+1):  # t = 1 ~ T
    if t > 1: print()  # 如果不是第 1 組測資,先換行
    print(f"Problem #{t:d}")
    line = input()  # 讀取一行字串
    idx_I = line.find("I=")  # 從字串中找 I= 的索引值
    idx_U = line.find("U=")  # 從字串中找 U= 的索引值
    idx_P = line.find("P=")  # 從字串中找 P= 的索引值
    I, U, P = 0, 0, 0  # 電流、電壓、電功率量值
    if idx_I != -1:  # 字串中有 I=
        tmp = ""  # 暫存量值用的字串
        for c in line[idx_I+2:]:  # 從 idx_I+2 開始讀取字元
            if c.isnumeric() or c == '.': tmp += c   # 如果 c 是數字或小數點,c 加到 tmp
            else:  # 如果 c 是其它字元
                I = float(tmp)  # tmp 轉成浮點數存入 I
                I *= prefix[c]  # 乘以前綴詞對應的倍率
                break  # 中止迴圈
    if idx_U != -1:  # 字串中有 U=
        tmp = ""  # 暫存量值用的字串
        for c in line[idx_U+2:]:  # 從 idx_U+2 開始讀取字元
            if c.isnumeric() or c == '.': tmp += c   # 如果 c 是數字或小數點,c 加到 tmp
            else:  # 如果 c 是其它字元
                U = float(tmp)  # tmp 轉成浮點數存入 U
                U *= prefix[c]  # 乘以前綴詞對應的倍率
                break  # 中止迴圈
    if idx_P != -1:  # 字串中有 P=
        tmp = ""  # 暫存量值用的字串
        for c in line[idx_P+2:]:  # 從 idx_P+2 開始讀取字元
            if c.isnumeric() or c == '.': tmp += c   # 如果 c 是數字或小數點,c 加到 tmp
            else:  # 如果 c 是其它字元
                P = float(tmp)  # tmp 轉成浮點數存入 P
                P *= prefix[c]  # 乘以前綴詞對應的倍率
                break  # 中止迴圈
    ### 印出答案 ###
    if idx_I == -1: print(f"I={P/U:.2f}A")  # 求電流
    elif idx_U == -1: print(f"U={P/I:.2f}V")  # 求電壓
    elif idx_P == -1: print(f"P={I*U:.2f}W")  # 求電功率


2026年3月18日 星期三

ZeroJudge 解題筆記:c126. 00536 - Tree Recovery

作者:王一哲
日期:2026年3月18日


ZeroJudge 題目連結:c126. 00536 - Tree Recovery

解題想法


這題需要知道二元樹的結構及走訪的順序。前序走訪的順序為父節點、左子節點、右子節點,中序走訪的順序為左子節點、父節點、右子節點,後序走訪的順序為左子節點、右子節點、父節點。因此,從測資中讀到的前序走訪資料開頭就是根節點,測資中的中序走訪資料左側為根節點的左子樹,右側為根節點的右子樹。依照這樣的原則設計從前序走訪、中序走訪資料建立後序走訪資料的函式。

Python 程式碼


用字串儲存二元樹走訪的資料,用時間約為 7 ms,記憶體約為 2.8 MB,通過測試。
import sys

def build_tree(preorder, inorder):
    if not preorder or not inorder: return ""  # 如果沒有資料,回傳空字串
    root = preorder[0]  # 前序走訪開頭為根節點
    root_idx = inorder.find(root)  # 找到中序走訪資料中根節點的索引值
    left_inorder = inorder[:root_idx]  # 中序走訪資料中左子樹資料
    right_inorder = inorder[root_idx+1:]  # 中序走訪資料中右子樹資料
    left_preorder = preorder[1:1+len(left_inorder)]  # 前序走訪資料中左子樹資料
    right_preorder = preorder[1+len(left_inorder):]  # 前序走訪資料中右子樹資料
    left_postorder = build_tree(left_preorder, left_inorder)  # 遞迴,找左子樹後序走訪資料
    right_postorder = build_tree(right_preorder, right_inorder)  # 遞迴,找右子樹後序走訪資料
    return left_postorder + right_postorder + root  # 組合成答案

result = []
lines = sys.stdin.readlines()
for line in lines:
    if not line.strip(): continue
    result.append(f"{build_tree(*line.split())}\n")
sys.stdout.write("".join(result))

用串列儲存二元樹走訪的資料,用時間約為 7 ms,記憶體約為 2.8 MB,通過測試。
import sys

def build_tree(preorder, inorder):
    if not preorder or not inorder: return []  # 如果沒有資料,回傳空串列
    root = preorder[0]  # 前序走訪開頭為根節點
    root_idx = inorder.find(root)  # 找到中序走訪資料中根節點的索引值
    left_inorder = inorder[:root_idx]  # 中序走訪資料中左子樹資料
    right_inorder = inorder[root_idx+1:]  # 中序走訪資料中右子樹資料
    left_preorder = preorder[1:1+len(left_inorder)]  # 前序走訪資料中左子樹資料
    right_preorder = preorder[1+len(left_inorder):]  # 前序走訪資料中右子樹資料
    left_postorder = build_tree(left_preorder, left_inorder)  # 遞迴,找左子樹後序走訪資料
    right_postorder = build_tree(right_preorder, right_inorder)  # 遞迴,找右子樹後序走訪資料
    return left_postorder + right_postorder + [root]  # 組合成答案

result = []
lines = sys.stdin.readlines()
for line in lines:
    if not line.strip(): continue
    ans = "".join(build_tree(*line.split()))
    result.append(f"{ans}\n")
sys.stdout.write("".join(result))


2026年3月17日 星期二

ZeroJudge 解題筆記:c125. 00534 - Frogger

作者:王一哲
日期:2026年3月17日


ZeroJudge 題目連結:c125. 00534 - Frogger

解題想法


先讀取所有石頭的位置,計算任意兩個石頭之間的距離,存入二維陣列 $dist$ 之中,其中 $dist[i][j]$ 代表石頭 $i, j$ 之間的距離。 開一個陣列 $ans$,儲存到達第 $i$ 個石頭的路徑上單次跳躍距離最大值之中的最小值。接下來用一個最小優先佇列 $pq$,資料為 $(距離, 石頭索引值)$,代表到第 $i$ 顆石頭的路徑上最大的單次跳躍距離,距離最小的資料放上面。如果 $pq$ 還有資料,而且 $pq$ 最上面的石頭索引值不等於 $1$(終點),while 迴圈繼續執行。每次取出 $pq$ 最上方的資料,目前的距離 $curr$、石頭索引值 $u$,掃過石頭 $v ~(0 \leq v \leq n-1)$,取 $curr$ 與 $dist[u][v]$ 較大者為新的距離 $new_{dis}$,如果 $new_{dis} < ans[v]$,更新 $ans[v]$,並將 ${new_{dis}, v}$ 加入 $pq$。

Python 程式碼


使用時間約為 37 ms,記憶體約為 4.1 MB,通過測試。
import sys, heapq

def cal_dis(a, b):  # 計算距離用的函式
    return ((a[0] - b[0])**2 + (a[1] - b[1])**2)**0.5

ca = 0
result = []
for line in sys.stdin:
    if not line.strip(): continue
    n = int(line)  # n 顆石頭
    if n == 0: break  # 中止迴圈的條件
    ca += 1
    if ca > 1: result.append("\n")  # 從第 2 筆測資開始要先空一行
    stones = [tuple(map(float, sys.stdin.readline().split())) for _ in range(n)]  # 石頭位置
    dist = [[0.0]*n for _ in range(n)]  # 石頭之間的距離
    for i in range(n):
        for j in range(n):
            if i == j: continue
            d = cal_dis(stones[i], stones[j])
            dist[i][j] = d
            dist[j][i] = d
    ans = [float('inf')]*n  # 到達第 i 個石頭的路徑上單次跳躍距離最大值之中的最小值
    pq = [(0.0, 0)]  #  (距離, 石頭索引值),代表到第 $i$ 顆石頭的路徑上最大的單次跳躍距離
    while pq and pq[0][1] != 1:  # pq 還有資料,而且 pq 最上面的石頭索引值不等於 1
        curr, u = heapq.heappop(pq)  # 取出 pq 最上方的資料,目前的距離 curr、石頭索引值 u
        for v in range(n):  # 掃過石頭 v = 0 ~ n-1
            if v == u: continue  # 同一顆石頭,跳過
            new_dis = max(curr, dist[u][v])  # 新的距離
            if new_dis < ans[v]:  # 新的最小值
                ans[v] = new_dis  # 更新 ans[v]
                heapq.heappush(pq, (new_dis, v))  # (new_dis, v) 加入 pq
    result.append(f"Scenario #{ca:d}\nFrog Distance = {ans[1]:.3f}\n")
sys.stdout.write("".join(result))


2026年3月16日 星期一

ZeroJudge 解題筆記:c124. 00532 - Dungeon Master

作者:王一哲
日期:2026年3月16日


ZeroJudge 題目連結:c124. 00532 - Dungeon Master

解題想法


這題可以用一個三維陣列儲存地圖及步數,0 代表起點、障礙物、邊界,-1 代表還沒有走過的格子,另外在地圖的邊緣加一圈 0 當作哨兵,避免在用 BFS 找最短路徑時出界。 由於測資中有一些多餘的空行,用 Python 解題時可以用 sys.stdin.read().split() 一次讀取所有測資、拆開、存入串列,這樣可以很方便地避開空行。

Python 程式碼


使用時間約為 52 ms,記憶體約為 4.2 MB,通過測試。
import sys
from collections import deque

result = []
data = sys.stdin.read().split()
ptr = 0
while ptr < len(data):
    L, R, C = map(int, data[ptr:ptr+3])  # 迷宮的高、深、寬,對應 z、y、x 座標
    ptr += 3
    if L == 0 and R == 0 and C == 0: break  # 中止迴圈的條件
    sx, sy, sz, ex, ey, ez = 0, 0, 0, 0, 0, 0  # 起點、終點座標
    maze = [[[0]*(C+1) for _ in range(R+1)] for _ in range(L+1)]  # 迷宮,0 是起點、邊界、石頭
    """ 讀取地圖資料,-1 代表還沒有走過 """
    for z in range(L):  # 依序讀取 L 層地圖
        for y in range(R):  # 讀取 R 列地圖
            line = data[ptr]
            ptr += 1
            for x, ch in enumerate(line):  # 依序讀取此列的字元
                if ch == 'S':  # 起點,步數為 0
                    sz, sy, sx = z, y, x
                    maze[z][y][x] = 0
                elif ch == 'E':  # 終點,-1 代表還沒有走過
                    ez, ey, ex = z, y, x
                    maze[z][y][x] = -1
                elif ch == '.':  # 走道,-1 代表還沒有走過
                    maze[z][y][x] = -1
    """ BFS 找最短路徑 """
    que = deque([(sz, sy, sx)])  # que 放入起點
    find_ans = False  # 是否有解
    while que and not find_ans:  # 如果 que 還有資料而且還沒有找到答案,繼續執行
        z, y, x = que.popleft()  # 取出 que 最前面的資料
        for dz, dy, dx in ((1, 0, 0), (-1, 0, 0), (0, 1, 0), (0, -1, 0), (0, 0, 1), (0, 0, -1)):  # 6 方位檢查
            nz, ny, nx = z+dz, y+dy, x+dx  # 要檢查 (nz, ny, nx)
            if maze[nz][ny][nx] != -1: continue  # 如果此格不是 -1,找下一格
            maze[nz][ny][nx] = maze[z][y][x] + 1  # 設定此格步數
            if nz == ez and ny == ey and nx == ex:  # 走到終點,找到答案,中止迴圈
                find_ans = True
                break
            que.append((nz, ny, nx))  # (nz, ny, nx) 加入 que
    """ End of BFS """
    if find_ans:
        result.append(f"Escaped in {maze[ez][ey][ex]:d} minute(s).\n")
    else:
        result.append("Trapped!\n")
sys.stdout.write("".join(result))


2026年3月15日 星期日

ZeroJudge 解題筆記:c122. 00443 - Humble Numbers

作者:王一哲
日期:2026年3月15日


ZeroJudge 題目連結:c122. 00443 - Humble Numbers

解題想法


用陣列 nums 儲存 humble number,再用另外 4 個變數 i2, i3, i5, i7 記錄 nums 之中前一個 2、3、5、7 倍數對應的索引值,每次更新時計算這 4 個索引值分別乘以 2、3、5、7 的值,取其中的最小值加入 nums 之中並更新對應的索引值,當 nums 的長度等於 5842 就可以停止運算。接下來讀取測資,從 nums 之中找答案。

Python 程式碼


使用時間約為 14 ms,記憶體約為 4.6 MB,通過測試。
import sys

# 計算前 5842 個Humble Number
nums = [0, 1]  # 配合編號,開頭多一個 0
i2, i3, i5, i7 = 1, 1, 1, 1  # 2, 3, 5, 7 倍數基底的索引值
while len(nums) <= 5842:  # 當 nums 長度小於、等於 5842 時繼續執行
    nxt2 = nums[i2] * 2  # 下一個 2 的倍數
    nxt3 = nums[i3] * 3  # 下一個 3 的倍數
    nxt5 = nums[i5] * 5  # 下一個 5 的倍數
    nxt7 = nums[i7] * 7  # 下一個 7 的倍數
    nxt_num = min(nxt2, nxt3, nxt5, nxt7)  # 取以上 4 個數之中的最小值加入 nums
    nums.append(nxt_num)
    if nxt_num == nxt2: i2 += 1  # 如果是 2 的倍數更新 i2
    if nxt_num == nxt3: i3 += 1  # 如果是 3 的倍數更新 i3
    if nxt_num == nxt5: i5 += 1  # 如果是 5 的倍數更新 i5
    if nxt_num == nxt7: i7 += 1  # 如果是 7 的倍數更新 i7

def last(x):  # 數字後面接的英文
     two = x%100  # 最後兩位數
     one = x%10  # 最後一位數
     if two == 11 or two == 12 or two == 13: return "th"  # 最後兩位數是 11、12、13,回傳 th
     if one == 1: return "st"  # 最後一位數是 1,回傳 st
     if one == 2: return "nd"  # 最後一位數是 2,回傳 nd
     if one == 3: return "rd"  # 最後一位數是 3,回傳 rd
     return "th"  # 其它,回傳 th

result = []
lines = sys.stdin.readlines()
for line in lines:
    if not line.strip(): continue
    n = int(line)
    if n == 0: break
    result.append(f"The {n:d}{last(n):s} humble number is {nums[n]:d}.\n")
sys.stdout.write("".join(result))


2026年3月14日 星期六

ZeroJudge 解題筆記:c121. 00495 - Fibonacci Freeze

作者:王一哲
日期:2026年3月14日


ZeroJudge 題目連結:c121. 00495 - Fibonacci Freeze

解題想法


由於題目要求計算到費氏數列第 5000 項,需要用到大數加法,如果用 Python 解題可以直接算,如果用 C++ 則要自己寫大數加法的函式。

Python 程式碼


使用時間約為 8 ms,記憶體約為 4.1 MB,通過測試。
import sys

fib = [0]*5001
fib[1] = 1
for i in range(2, 5001):
    fib[i] = fib[i-1] + fib[i-2]

for line in sys.stdin:
    if not line.strip(): continue
    n = int(line)
    print(f"The Fibonacci number for {n:d} is {fib[n]:d}")


2026年3月13日 星期五

ZeroJudge 解題筆記:c117. 00439 - Knight Moves

作者:王一哲
日期:2026年3月13日


ZeroJudge 題目連結:c117. 00439 - Knight Moves

解題想法


為了便於計算,會將棋盤的座標改成 0-indexed。用一個二維串列記錄從起點走到每一格的步數,預設值 -1 代表未走訪,起點步數為 0,用 BFS 找步數。

Python 程式碼


使用時間約為 95 ms,記憶體約為 4.6 MB,通過測試。
import sys
from collections import deque

result = []
data = sys.stdin.read().split()
ptr = 0
while ptr < len(data):
    start, end = data[ptr:ptr+2]  # 起點,終點
    ptr += 2
    if start == end:  # 特例,不需要移動
        result.append(f"To get from {start:s} to {end:s} takes 0 knight moves.\n")
        continue
    grid = [[-1]*8 for _ in range(8)]  # 棋盤上走到每格的步數,-1 代表還沒有走過
    c1 = ord(start[0]) - ord('a')  # 起點座標 (r1, c1),改成 0-index
    r1 = int(start[1]) - 1
    c2 = ord(end[0]) - ord('a')  # 終點座標 (r2, c2),改成 0-index
    r2 = int(end[1]) - 1
    grid[r1][c1] = 0  # 起點步數 0
    que = deque([(r1, c1)])  # 待走訪佇列
    find_ans = False  # 是否找到答案
    while que and not find_ans:  # BFS,如果 que 還有資料而且還沒有找到答案,繼續執行
        r, c = que.popleft()  # 目前所在的位置 (r, c)
        for dr, dc in ((1, 2), (2, 1), (2, -1), (1, -2), (-1, -2), (-2, -1), (-2, 1), (-1, 2)):  # 八方位檢查
            nr, nc = r+dr, c+dc  # 正在檢查 (nr, nc)
            if 0 <= nr < 8 and 0 <= nc < 8 and grid[nr][nc] == -1:  # 未出界也未走訪
                grid[nr][nc] = grid[r][c] + 1  # 更新 (nr, nc) 的步數
                if nr == r2 and nc == c2:  # 走到終點
                    find_ans = True  # 已經找到答案
                    break  # 中止迴圈
                que.append((nr, nc))  # 將 (nr, nc) 加入 que
    result.append(f"To get from {start:s} to {end:s} takes {grid[r2][c2]:d} knight moves.\n")
sys.stdout.write("".join(result))


2026年3月12日 星期四

ZeroJudge 解題筆記:c116. 00438 - The Circumference of the Circle

作者:王一哲
日期:2026年3月12日


ZeroJudge 題目連結:c116. 00438 - The Circumference of the Circle

解題想法


這題考數學。假設三角形 $ABC$,三個角的對邊長度分別為 $a, b, c$,三角形面積為 $$ area = \frac{1}{2}ab \sin C $$ 假設外接圓半徑為 $R$,由正弦定理可得 $$ \frac{a}{\sin A} = \frac{b}{\sin B} = \frac{c}{\sin C} = 2R ~\Rightarrow~ \sin C = \frac{c}{2R} $$ 再代回面積公式 $$ area = \frac{1}{2} ab \cdot \frac{c}{2R} = \frac{abc}{4R} ~\Rightarrow~ R = \frac{abc}{4 area} $$ 其中三角形面積可以用海龍公式求解 $$ area = \sqrt{s(s-a)(s-b)(s-c)} ~~~~~ s = \frac{a+b+c}{2} $$ 最後在計算圓周長 $2 \pi R$ 時,$\pi$ 要用題目給的 $3.141592653589793$。

Python 程式碼


使用時間約為 7 ms,記憶體約為 2.9 MB,通過測試。
import sys, math

for line in sys.stdin:
    x1, y1, x2, y2, x3, y3 = map(float, line.split())
    a = ((x1 - x2)**2 + (y1 - y2)**2)**0.5
    b = ((x2 - x3)**2 + (y2 - y3)**2)**0.5
    c = ((x3 - x1)**2 + (y3 - y1)**2)**0.5
    s = (a+b+c) / 2
    area = (s*(s-a)*(s-b)*(s-c))**0.5
    R = a*b*c/(4*area)
    print(f"{2*3.141592653589793*R:.2f}")


2026年3月11日 星期三

ZeroJudge 解題筆記:c115. 00437 - The Tower of Babylon

作者:王一哲
日期:2026年3月11日


ZeroJudge 題目連結:c115. 00437 - The Tower of Babylon

解題想法


因為方塊可以旋轉,在讀取方塊邊長時,先將邊長排序,假設邊長依序為 $x, y, z$,將三種邊長排列方式都存入串列 blocks 之中。接下來將 blocks 依照長、寬由大到小排序,用 dp 計算最大高度。

Python 程式碼


使用時間約為 16 ms,記憶體約為 3 MB,通過測試。
import sys

ca = 0
result = []
for line in sys.stdin:
    if not line.strip(): continue
    n = int(line)  # 積木數量
    if n == 0: break  # 中止迴圈條件
    ca += 1
    blocks = []  # 可能的積木尺寸
    for _ in range(n):  # 讀取 n 行資料
        x, y, z = sorted(map(int, sys.stdin.readline().split()))  # x >= y >= z
        blocks.append((z, y, x))  # 取長、寬較大者放前面
        blocks.append((z, x, y))
        blocks.append((y, x, z))
    blocks.sort(key = lambda b : (b[0], b[1]), reverse=True)  # 依照長、寬由大到小排序
    m = len(blocks)  # 可用的積木數量
    dp = [0]*m  # 以第 i 塊為最上方的積木,累計的最大高度
    for i in range(m):  # 依序掃過第 0 ~ m-1 塊積木
        xi, yi, zi = blocks[i]  # 第 i 塊的 x、y、z 大小
        dp[i] = zi  # 如果只放第 i 塊,高度為 zi
        for j in range(i):  # 依序掃過第 0 ~ i-1 塊積木
            xj, yj = blocks[j][0], blocks[j][1]
            if xj > xi and yj > yi:  # 第 i 塊可以放在第 j 塊上面
                dp[i] = max(dp[i], dp[j] + zi)  # 取第 i 塊目前的高度、第 j 塊的高度加上 zi 較大者
    result.append(f"Case {ca:d}: maximum height = {max(dp):d}\n")
sys.stdout.write("".join(result))


2026年3月10日 星期二

ZeroJudge 解題筆記:c114. 00409 - Excuses, Excuses!

作者:王一哲
日期:2026年3月10日


ZeroJudge 題目連結:c114. 00409 - Excuses, Excuses!

解題想法


我是用集合儲存關鍵字,用串列儲存答案。讀取一行藉口之後,計算這行之中的關鍵字數量,如果數量是新的最大值,將儲存答案的串列清空、只存入這一行;如果數量等於目前的最大值,這一行加入儲存答案的串列之中。

Python 程式碼


使用時間約為 7 ms,記憶體約為 3 MB,通過測試。
import sys

ca = 0
result = []
for line in sys.stdin:
    if not line.strip(): continue
    ca += 1
    if ca > 1: result.append("\n")
    result.append(f"Excuse Set #{ca:d}\n")
    n, m = map(int, line.split())  # n 個關鍵字,m 行藉口
    keyword = {sys.stdin.readline().strip() for _ in range(n)}  # 關鍵字集合
    worst = []  # 最爛的藉口
    imax = 0  # 最爛的藉口關鍵字數量
    for _ in range(m):  # 讀取 m 行
        excuse = sys.stdin.readline().rstrip()  # 藉口
        cnt = 0  # 這個藉口的關鍵字數量
        word = []  # 暫存字母用的串列
        for c in excuse:  # 依序讀取字元 c
            if c.isalpha():  # 如果 c 是字母
                word.append(c.lower())  # 轉成小寫加入 word
            else:  # 反之,將 word 組成字串,如果是關鍵字,cnt 加 1
                if "".join(word) in keyword: cnt += 1
                word.clear()  # 漬空 word
        if "".join(word) in keyword: cnt += 1  # 最後要再結算一次
        word.clear()  # 漬空 word
        if cnt > imax:  # 如果 cnt 大於 imax
            imax = cnt  # 更新 imax
            worst = [excuse]  # 找到新的最爛藉口
        elif cnt == imax:  # 如果 cnt 等於 imax
            worst.append(excuse)  # 最爛藉口加一
    for s in worst: result.append(f"{s}\n")
sys.stdout.write("".join(result))


2026年3月9日 星期一

ZeroJudge 解題筆記:c113. 00378 - Intersecting Lines

作者:王一哲
日期:2026年3月9日


ZeroJudge 題目連結:c113. 00378 - Intersecting Lines

解題想法


直線的方程式可以寫成 $$ ax + by + c = 0 $$ 如果線上的兩個點的座標為 $(x_1, y_1)$、$(x_2, y_2)$,代入上式可得 $$ ax_1 + by_1 + c = 0 ~~~~~ ax_2 + by_2 + c = 0 $$ 兩式相減可得 $$ ax_1 - ax_2 + by_1 - by_2 = 0 ~\Rightarrow~ a(x_1 - x_2) = b(y_2 - y_1) $$ $$ a = y_2 - y_1 ~~~~~ b = x_1 - x_2 ~~~~~ c = -ax_1 - by_1 = x_1(y_1 - y_2) + y_1(x_2 - x_1) $$ 題目給定另一條直線上的座標為 $(x_3, y_3)$、$(x_4, y_4)$,則方程式 $a'x + b'y + c' = 0$ 的係數為 $$ a' = y_4 - y_3 ~~~~~ b' = x_3 - x_4 ~~~~~ c' = -a'x_3 - b'y_3 = x_3(y_3 - y_4) + y_3(x_4 - x_3) $$ 如果兩條直線平行,則 $$ \frac{a}{b} = \frac{a'}{b'} ~\Rightarrow~ ab' = a'b $$ 如果兩條直線重合,除了上式之外還要再加上另外兩個條件 $$ \frac{a}{c} = \frac{a'}{c'} ~\Rightarrow~ ac' = a'c ~~~~~ \frac{b}{c} = \frac{b'}{c'} ~\Rightarrow~ bc' = b'c $$ 如果兩條直線只有一個交點,則 $$ ab'x + bb'y + cb' - (a'bx + b'by + c'b) = 0 ~\Rightarrow~ x = \frac{c'b - cb'}{ab' - a'b} $$ $$ aa'x + ba'y + ca' - (a'ax + b'ay + c'a) = 0 ~\Rightarrow~ y = \frac{c'a - ca'}{ba' - b'a} $$

Python 程式碼


使用時間約為 7 ms,記憶體約為 2.9 MB,通過測試。
def line_equation(x1, y1, x2, y2):
    a = y2 - y1
    b = x1 - x2
    c = x1*(y1 - y2) + y1*(x2 - x1)
    return a, b, c

print("INTERSECTING LINES OUTPUT")
n = int(input())
for _ in range(n):
    x1, y1, x2, y2, x3, y3, x4, y4 = map(int, input().split())
    a, b, c = line_equation(x1, y1, x2, y2)
    ap, bp, cp = line_equation(x3, y3, x4, y4)
    if a*bp == ap*b:
        if a*cp == ap*c and b*cp == c*bp: print("LINE")
        else: print("NONE")
    else:
        x = (cp*b - c*bp) / (a*bp - ap*b)
        y = (cp*a - c*ap) / (b*ap - bp*a)
        print(f"POINT {x:.2f} {y:.2f}")
print("END OF OUTPUT")


2026年3月8日 星期日

ZeroJudge 解題筆記:c110. 00311 - Packets

作者:王一哲
日期:2026年3月8日


ZeroJudge 題目連結:c110. 00311 - Packets

解題想法


如果面積 $6 \times 6$ 的商品數量為 $n_6$,需要的箱子數量就是 $n_6$,而且沒有剩下的空間。 如果面積 $5 \times 5$ 的商品數量為 $n_5$,需要的箱子數量為 $n_5$,每一個箱子會剩下面積為 $11$ 的空間,總空間為 $11 n_5$,可以放入面積 $1 \times 1$ 的商品。 如果面積 $4 \times 4$ 的商品數量為 $n_4$,需要的箱子數量為 $n_4$,每一個箱子會剩下面積為 $20$ 的空間,總空間為 $20 n_4$,可以放入面積 $2 \times 2$ 的商品最多 $5$ 個,如果還有剩下的空間,可以再放入面積 $1 \times 1$ 的商品。下圖的 O 代表可用空間,X 代表已被面積 $4 \times 4$ 商品佔用的空間。
OOOOOO
OOOOOO
XXXXOO
XXXXOO
XXXXOO
XXXXOO
如果面積 $3 \times 3$ 的商品數量為 $n_3$,一個箱子最多可以放入 $4$ 個面積 $3 \times 3$ 的商品,需要的箱子數量 $$ p_3 = \left \lceil \frac{n_3}{4} \right \rceil $$ 如果 $n_3$ 是 $4$ 的倍數,不會有剩下的空間;如果 $mod(n_3, 4) = 1$ 剩下面積為 $27$ 的空間,可以放入面積 $2 \times 2$ 的商品最多 $5$ 個;如果 $mod(n_3, 4) = 2$ 剩下面積為 $18$ 的空間,可以放入面積 $2 \times 2$ 的商品最多 $3$ 個;如果 $mod(n_3, 4) = 3$ 剩下面積為 $9$ 的空間,可以放入面積 $2 \times 2$ 的商品最多 $1$ 個。如果還有剩下的空間,可以再放入面積 $1 \times 1$ 的商品。下圖的 O 代表可用空間,X 代表已被面積 $3 \times 3$ 商品佔用的空間。
OOOOOO     OOOOOO     XXXOOO
OOOOOO     OOOOOO     XXXOOO
OOOOOO     OOOOOO     XXXOOO
XXXOOO     XXXXXX     XXXXXX
XXXOOO     XXXXXX     XXXXXX
XXXOOO     XXXXXX     XXXXXX
如果面積 $2 \times 2$ 的商品數量為 $n_2$,一個箱子最多可以放入 $9$ 個面積 $2 \times 2$ 的商品,需要的箱子數量 $$ p_2 = \left \lceil \frac{n_2}{9} \right \rceil $$ 如果 $n_2$ 是 $9$ 的倍數,不會有剩下的空間;如果 $mod(n_2, 9) = r_2$ 剩下面積為 $36 - 4 r_2$ 的空間,可以放入面積 $1 \times 1$ 的商品。 如果面積 $1 \times 1$ 的商品數量為 $n_1$,一個箱子最多可以放入 $36$ 個面積 $1 \times 1$ 的商品,需要的箱子數量 $$ p_1 = \left \lceil \frac{n_1}{36} \right \rceil $$ 最後答案為 $ans = p_1 + p_2 + p_3 + n_4 + n_5 + n_6$


Python 程式碼


使用時間約為 10 ms,記憶體約為 3.2 MB,通過測試。
import sys

for line in sys.stdin:
    if not line.strip(): continue
    n1, n2, n3, n4, n5, n6 = map(int, line.split())
    if n1 == 0 and n2 == 0 and n3 == 0 and n4 == 0 and n5 == 0 and n6 == 0: break
    """ 放入 5*5 的商品 """
    s5 = 11*n5  # 放 5*5 商品箱子剩下的空間
    if s5 >= n1:  # 可以放入所有 1*1 商品
        s5 -= n1; n1 = 0
    else:  # 無法放入所有 1*1 商品
        n1 -= s5; s5 = 0
    """ 放入 4*4 的商品 """
    s4 = 20*n4  # 放 4*4 商品箱子剩下的空間
    if s4 >= n2*4:  # 可以放入所有 2*2 商品
        s4 -= n2*4; n2 = 0
    else:  # 無法放入所有 2*2 商品
        n2 -= s4//4; s4 = 0
    if s4 > 0 and n1 > 0:  # s4 還有空間而且還有 1*1 商品要放
        if s4 >= n1:  # 可以放入所有 1*1 商品
            s4 -= n1; n1 = 0
        else:  # 無法放入所有 1*1 商品
            n1 -= s4; s4 = 0
    """ 放入 3*3 的商品 """
    r3 = n3%4  # 沒放滿的箱子中 3*3 商品數量
    p3 = n3//4 + (r3 > 0)  # 3*3 商品使用的箱子數量
    s3 = 0  # 沒放滿的箱子剩下的面積
    if r3 == 1:  # 放 1 個 3*3 商品
        s3 = 27  # 面積剩下 27
        if n2 <= 5:  # 可以放入所有 2*2 商品
            s3 -= n2*4; n2 = 0
        else:  # 無法放入所有 2*2 商品
            s3 -= 20; n2 -= 5
    elif r3 == 2:  # 放 2 個 3*3 商品
        s3 = 18  # 面積剩下 18
        if n2 <= 3:  # 可以放入所有 2*2 商品
            s3 -= n2*4; n2 = 0
        else:  # 無法放入所有 2*2 商品
            s3 -= 12; n2 -= 3
    elif r3 == 3:  # 放 3 個 3*3 商品
        s3 = 9  # 面積剩下 9
        if n2 == 1:  # 可以放入所有 2*2 商品
            s3 -= 4; n2 = 0
        elif n2 > 1:  # 無法放入所有 2*2 商品
            s3 -= 4; n2 -= 1
    if s3 > 0 and n1 > 0:  # s3 還有空間而且還有 1*1 商品要放
        if s3 >= n1:  # 可以放入所有 1*1 商品
            s3 -= n1; n1 = 0
        else:  # 無法放入所有 1*1 商品
            n1 -= s3; s3 = 0
    """ 放入 2*2 的商品 """
    r2 = n2%9  # 沒放滿的箱子中 3*3 商品數量
    p2 = n2//9 + (r2 > 0)  # 2*2 商品使用的箱子數量
    s2 = 0 if r2 == 0 else 36 - r2*4  # 沒放滿的箱子剩下的面積
    if s2 > 0 and n1 > 0:  # s2 還有空間而且還有 1*1 商品要放
        if s2 >= n1:  # 可以放入所有 1*1 商品
            s2 -= n1; n1 = 0
        else:  # 無法放入所有 1*1 商品
            n1 -= s2; s2 = 0
    """ 放入 1*1 的商品 """
    p1 = n1//36 + (n1%36 > 0)
    """ 印出答案 """
    print(p1 + p2 + p3 + n4 + n5 + n6)


2026年3月7日 星期六

ZeroJudge 解題筆記:c106. 00271 - Simply Syntax

作者:王一哲
日期:2026年3月7日


ZeroJudge 題目連結:c106. 00271 - Simply Syntax

解題想法


原文的題目連結,其中條件的部分為
    The only characters in the language are the characters 'p' through 'z' and 'N', 'C', 'D', 'E', and 'I'. 句子之中只有英文小寫字母 p 到 z、大寫字母 N, C, D, E, I。
  1. Every character from 'p' through 'z' is a correct sentence. 英文小寫字母 p 到 z 是合法的。
  2. If s is a correct sentence, then so is Ns. 如果某個單字 s 是合法的,則 Ns 也是合法的,注意,這裡面的 s 不是專門指小寫字母 s
  3. If s and t are correct sentences, then so are Cst, Dst, Est and Ist. 如果某兩個單字 st 是合法的,則 Cst、Dst、Est 及 Ist 也是合法的,注意,這裡面的 st 不是專門指小寫字母 st
  4. Rules 0. to 3. are the only rules to determine the syntactical correctness of a sentence. 只有規則 0 到 3,沒有其它的規則。


Python 程式碼


使用時間約為 7 ms,記憶體約為 2.9 MB,通過測試。
import sys

def check(s):
    st = []  # 有效字元的堆疊
    for c in s[::-1]:  # 由後向前讀取
        if c in "pqrstuvwxyz":  # c 是 pqrstuvwxyz
            st.append(c)  # c 放入 st
        elif c == 'N':  # 如果 c 是 N
            if not st:  # 如果 st 是空的
                return False  # 回傳 False
            a = st.pop()  # 取出 st 最後一項
            st.append(c+a)  # c+a 加入 st
        elif c in "CDEI":  # 如果 c 是 CDEI
            if len(st) < 2:  # 如果 st 不到兩項資料
                return False  # 回傳 False
            a = st.pop()  # 取出 st 最後兩項
            b = st.pop()
            st.append(c+a+b)  # c+a+b 加入 st
        else:  # 其它狀況,c 不合法
            return False  # 回傳 False
    return len(st) == 1  # 如果 st 只有一筆資料,整個字串合法

result = []
for line in sys.stdin:
    if not line.rstrip(): continue
    result.append("YES\n" if check(line.rstrip()) else "NO\n")
sys.stdout.write("".join(result))

2026年3月6日 星期五

ZeroJudge 解題筆記:c104. 00167 - The Sultan's Successors

作者:王一哲
日期:2026年3月6日


ZeroJudge 題目連結:c104. 00167 - The Sultan's Successors

解題想法


八皇后的變型,主要考遞迴跟回溯。

Python 程式碼


使用時間約為 21 ms,記憶體約為 3 MB,通過測試。
import sys

def solve(grid):  # 主要的解題函式
    rows = [-1]*8  # 目前皇后放置的列
    cols, dia1, dia2 = set(), set(), set()  # 已用的欄、左上右下對角線、右上左下對角線
    imax = 0  # 最高分,預設為 0
    ### 回溯,代入列、目前的分數 ###
    def backtrack(row, curr):
        nonlocal imax  # 這樣才能改變外層 imax 的值
        if row == 8:  # 遞迴出口,已放置 8 個皇后
            imax = max(imax, curr)  # 更新 imax
        # 試著在此列每一欄放置皇后
        for col in range(8):
            # 檢查欄、左上右下、右上左下是否已被佔用
            if col not in cols and (row-col) not in dia1 and (row+col) not in dia2:
                # 可以放置皇后
                rows[row] = col
                cols.add(col)
                dia1.add(row-col)
                dia2.add(row+col)
                # 遞迴到下一列,代入加上 (row, col) 的分數
                backtrack(row+1, curr+grid[row][col])
                # 回溯,移除皇后,恢復佔用的狀態
                rows[row] = -1
                cols.remove(col)
                dia1.remove(row-col)
                dia2.remove(row+col)
    ### End of backtrack ###
    backtrack(0, 0)  # 從第 0 列、分數 0 開始回溯
    return imax  # 回傳最高分

idx = 0
lines = sys.stdin.readlines()
t = int(lines[idx])
idx += 1
for _ in range(t):
    grid = [list(map(int, line.split())) for line in lines[idx:idx+8]]
    idx += 8
    imax = solve(grid)
    sys.stdout.write(f"{imax:5d}\n")


2026年3月5日 星期四

ZeroJudge 解題筆記:c103. 00131 - The Psychic Poker Player

作者:王一哲
日期:2026年3月5日


ZeroJudge 題目連結:c103. 00131 - The Psychic Poker Player

解題想法


這題我只寫了 Python 版本。我先為每一種牌型寫一個自訂函式,再用另一個自訂函式 check 檢查手牌的牌型,由高分檢查到低分。最後再寫一個自訂函式 check 測試各種換牌張數的最高分。討論區裡有一種用數學計算的寫法,但我沒有認真推導數學關係式,就沒有寫了。

Python 程式碼


使用時間約為 10 ms,記憶體約為 3.4 MB,通過測試。
import sys, itertools

def flush(hand):  # 手牌是否為同花
    return all(card[1] == hand[0][1] for card in hand[1:])

def straight(hand):  # 手牌是否為順子
    nums = {'A': 14, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7,
            '8': 8, '9':9, 'T': 10, 'J': 11, 'Q': 12, 'K': 13}  # 牌面數字
    flag1, flag2 = True, True  # 是否為 A, 2, 3, 4, 5,是否為其它號碼的順子
    if hand[-1][0] == 'A':  # 特例,最大的是 A,要檢查是否為 A, 2, 3, 4, 5
        pre = 1  # A 當作 1
        for card in hand[:-1]:
            curr = nums[card[0]]
            if curr != pre+1:
                flag1 = False
                break
            pre = curr
    else: flag1 = False  # 最大的不是 A,不可能是 A, 2, 3, 4, 5
    # 其它號碼的順子
    pre = nums[hand[0][0]]
    for card in hand[1:]:
        curr = nums[card[0]]
        if curr != pre+1:
            flag2 = False
            break
        pre = curr
    return flag1 or flag2

def straight_flush(hand):  # 手牌是否為同花順
    return flush(hand) and straight(hand)

def four_of_a_kind(hand):  # 手牌是否為鐵支
    cnt = dict()  # 計數器
    for card in hand:  # 依序讀取手牌數值
        n = card[0]
        if n not in cnt: cnt[n] = 1
        else: cnt[n] += 1
    return max(cnt.values()) == 4  # 如果某一種數值有 4 張,是鐵支

def full_house(hand):  # 手牌是否為葫蘆
    cnt = dict()  # 計數器
    for card in hand:  # 依序讀取手牌數值
        n = card[0]
        if n not in cnt: cnt[n] = 1
        else: cnt[n] += 1
    tmp = sorted(cnt.values())  # 牌面數量排序
    return len(tmp) == 2 and tmp[0] == 2 and tmp[1] == 3

def three_of_a_kind(hand):  # 手牌是否為三條
    cnt = dict()  # 計數器
    for card in hand:  # 依序讀取手牌數值
        n = card[0]
        if n not in cnt: cnt[n] = 1
        else: cnt[n] += 1
    tmp = sorted(cnt.values())  # 牌面數量排序
    return len(tmp) == 3 and tmp[0] == 1 and tmp[1] == 1 and tmp[2] == 3

def two_pairs(hand):  # 手牌是否為兩對
    cnt = dict()  # 計數器
    for card in hand:  # 依序讀取手牌數值
        n = card[0]
        if n not in cnt: cnt[n] = 1
        else: cnt[n] += 1
    tmp = sorted(cnt.values())  # 牌面數量排序
    return len(tmp) == 3 and tmp[0] == 1 and tmp[1] == 2 and tmp[2] == 2

def one_pair(hand):  # 手牌是否為一對
    cnt = dict()  # 計數器
    for card in hand:  # 依序讀取手牌數值
        n = card[0]
        if n not in cnt: cnt[n] = 1
        else: cnt[n] += 1
    tmp = sorted(cnt.values())  # 牌面數量排序
    return len(tmp) == 4 and tmp[0] == 1 and tmp[1] == 1 and tmp[2] == 1 and tmp[3] == 2

def check(hand):
    if straight_flush(hand): return 8
    if four_of_a_kind(hand): return 7
    if full_house(hand): return 6
    if flush(hand): return 5
    if straight(hand): return 4
    if three_of_a_kind(hand): return 3
    if two_pairs(hand): return 2
    if one_pair(hand): return 1
    return 0

def change(hand, deck):  # 輸入手牌、牌庫,依序檢查
    result = [[0, i] for i in range(6)]  # 從牌庫換 0 ~ 5 張牌可組成的最大組合,[組合, 換牌數量]
    result[0][0] = check(hand)
    # 換一張牌,測試換第一張 ~ 第五張的效果
    best = 0
    for i in range(5):
        tmp = hand[:i] + hand[i+1:] + [deck[0]]
        tmp.sort(key = lambda x : (nums[x[0]], shdc[x[1]]))
        best = max(best, check(tmp))
    result[1][0] = best
    # 換二張牌,測試換其中兩張牌的效果
    best = 0
    for i, j in itertools.combinations(range(5), 2):
        tmp = hand.copy()
        tmp.remove(hand[i])
        tmp.remove(hand[j])
        tmp += deck[:2]
        tmp.sort(key = lambda x : (nums[x[0]], shdc[x[1]]))
        best = max(best, check(tmp))
    result[2][0] = best
    # 換三張牌,測試換其中三張牌的效果
    best = 0
    for i, j, k in itertools.combinations(range(5), 3):
        tmp = hand.copy()
        tmp.remove(hand[i])
        tmp.remove(hand[j])
        tmp.remove(hand[k])
        tmp += deck[:3]
        tmp.sort(key = lambda x : (nums[x[0]], shdc[x[1]]))
        best = max(best, check(tmp))
    result[3][0] = best
    # 換四張牌,測試只留其中一張牌的效果
    best = 0
    for i in range(5):
        tmp = [hand[i]] + deck[:4]
        tmp.sort(key = lambda x : (nums[x[0]], shdc[x[1]]))
        best = max(best, check(tmp))
    result[4][0] = best
    # 全換
    result[5][0] = check(deck)
    result.sort(reverse=True)
    return result[0]

for line in sys.stdin:
    hand = line.split()[:5]
    deck = line.split()[5:]
    nums = {'A': 14, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7,
            '8': 8, '9':9, 'T': 10, 'J': 11, 'Q': 12, 'K': 13}  # 牌面數字
    shdc = {'S': 0, 'H': 1, 'D': 2, 'C': 3}  # 花色
    comb = ("highest-card", "one-pair", "two-pairs", "three-of-a-kind", "straight",
            "flush", "full-house", "four-of-a-kind", "straight-flush")
    sorted_hand = sorted(hand, key = lambda x : (nums[x[0]], shdc[x[1]]))  # 數字、花色由小到大排序
    best_comb, change_cards = change(sorted_hand, deck)
    print("Hand: ", end="")
    print(*hand, end=" ")
    print("Deck: ", end="")
    print(*deck, end=" ")
    print(f"Best hand: {comb[best_comb]:s}")


2026年3月4日 星期三

ZeroJudge 解題筆記:c102. 00128 - Software CRC

作者:王一哲
日期:2026年3月4日


ZeroJudge 題目連結:c102. 00128 - Software CRC

解題想法


這題會用到字元的 ASCII 編碼,因為編碼為 0 到 255 的整數,可以用 1 個位元組 (byte)、8 個位元 (bit)、2 位數的 16 進位整數表示。題目的意思應該是將讀到的字串,逐一按照字元的 ASCII 編碼排成很長一列的 16 進位整數,最後再加上兩個 16 進位整數的 CRC 碼,讓這個很長的 16 進位整數可以被指定的 10 進位整數 34943 整除。題目要求的就是每一行測資對應的 CRC 碼。解題過程如下:
  1. 假設字串對應的整數儲存於變數 m,從字串 s 最前面依序讀取字元 c,將 m 向左平移 8 位元之後加上 c 的 ASCII 編碼。為了避免 m 溢位,可以在每一次計算後對 g 取餘數。
  2. 將 m 向左平移 16 位元,增加給 CRC 碼的空間,再對 g 取餘數 r。
  3. 如果 r 等於 0,CRC 碼就是 0;反之,CRC 碼為 g-r。
  4. 將 CRC 碼向右平移 8 位元再與 0XFF 取 &,可以得到第一個 byte 的值;將 CRC 碼與 0XFF 取 &,可以得到第二個 byte 的值。


Python 程式碼


使用時間約為 7 ms,記憶體約為 2.8 MB,通過測試。
import sys

def solve(s):
    g, m = 34943, 0  # 取餘數用的除數,字串編碼對應的整數
    for c in s:  # 依序讀取 s 的字元 c
        m = ((m << 8) + ord(c)) % g  # m 向左平移 8 位元後加上 c 的 ASCII 碼,再取餘數
    m <<= 16  # m 向左平移 16 位元,增加給 CRC 碼的空間
    r = m%g  # m 對 g 取餘數
    crc = 0  # CRC  碼預設為 0
    if r > 0: crc = g-r  # 如果 r 大於 0,CRC  碼改成 g-r
    byte1 = (crc >> 8) & 0xFF  # CRC  碼向右平移 8 位元,與 0xFF 取 &
    byte2 = crc & 0xFF  # CRC  碼與 0xFF 取 &
    print(f"{byte1:02X} {byte2:02X}")  # 將兩個位元組用 16 進位輸出

for line in sys.stdin:
    line = line.rstrip()
    if line == "#": break
    solve(line)


2026年3月3日 星期二

ZeroJudge 解題筆記:c098. 00106 - Fermat vs. Pythagoras

作者:王一哲
日期:2026年3月3日


ZeroJudge 題目連結:c098. 00106 - Fermat vs. Pythagoras

解題想法


這題困難之處在於找數學關係,不能用暴力法跑兩層迴圈,一定會超時。 畢氏三元組 (Pythagorean triples) 的定義為滿足 $x^2 + y^2 = z^2$ 的正整數為 $(x, y, z)$。原始畢氏三元組 (Primitive Pythagorean triples) 還要再加上 $x, y, z$ 互質的條件,也就是 $gcd(x, y, z) = 1$。需要用到的性質為 $$ x = m^2 - n^2, ~~~ y = 2mn, ~~~ z = m^2 + n^2 $$ 其中
  • $m > n> 0$,且 $m, n$ 為整數。
  • $m, n$ 互質,即 $gcd(m, n) = 1$
  • $m - n$ 為奇數,即 $m, n$ 為一個奇數、一個偶數。

推導過程

直角三角形邊長 $x^2 + y^2 = z^2$,其中 $z$ 是斜邊。因為 $(x, y, z)$ 互質,不可能有 2 個以上的偶數,所以
  • $x, z$ 是奇數
  • $y$ 是偶數
假設 $y \equiv 2k$,其中 $k$ 為正整數,則 $$ x^2 + (2k)^2 = z^2 ~\Rightarrow~ x^2 + 4k^2 = z^2 ~\Rightarrow~ z^2 - x^2 = 4k^2 ~\Rightarrow~ (z+x)(z-x) = 4k^2 $$ 假設 $a = z-x, ~b = z+x$,因為 $z, x$ 是奇數,所以 $a, b$ 是偶數。假設 $a = 2p, ~b = 2q$,則 $$ 2p \cdot 2q = (2k)^2 ~\Rightarrow~ pq = k^2 $$ 因為 $x, z$ 互質,所以 $p, q$ 互質,為了符合 $pq = k^2$,$p, q$ 必須是平方數。 假設 $p = n^2,~ q = m^2$,由於 $$ a = z-x = 2p = 2n^2, ~~~ b = z+x = 2q = 2m^2 $$ 由 $a+b$ 可以解 $z$ $$ a+b = 2z ~\Rightarrow~ 2(m^2 + n^2) = 2z ~\Rightarrow~ z = m^2 + n^2 $$ $$ x = z - a = m^2 + n^2 - 2n^2 = m^2 - n^2 $$ $$ y = \sqrt{z^2 - x^2} = \sqrt{(m^2 + n^2)^2 - (m^2 - n^2)^2} = \sqrt{4m^2 n^2} = 2mn $$

Python 程式碼


使用時間約為 0.4 s,記憶體約為 60.5 MB,通過測試。
import sys, math

def solve(maxn):
    status = [False]*(maxn+1)  # 0 ~ maxn 是否為畢氏三元組
    coprime = [0]*(maxn+1)  # 0 ~ maxn 互質三元組數量前綴和
    """
     計算 0 ~ maxn 之間的原始畢氏三元組與所有畢氏三元組數量
     依序檢查 m = 2 ~ sqrt(maxn), n = 1 ~ m-1
     如果 m-n 是奇數,m, n 互質,則 x = m^2 - n^2, y = 2mn, z = m^2 + n^2
     為原始畢氏三元組,再將 (x, y, z) 乘以整數倍,找其它的畢氏三元組
    """
    for m in range(2, int(maxn**0.5) + 1):
        for n in range(1, m):
            if (m-n)%2 == 1 and math.gcd(m, n) == 1:
                x, y, z = m*m - n*n, 2*m*n, m*m + n*n
                if z > maxn: continue  # z 超過上限,找下一組
                coprime[z] += 1  # z 對應的原始畢氏三元組數量加 1
                k = 1  # 原始畢氏三元組的 k 倍,如果 k > 1 為不互質的三元組
                while k*z <= maxn:  # 如果 k*z 還沒有超過上限繼續執行
                    status[k*x], status[k*y], status[k*z] = True, True, True
                    k += 1
    # coprime 改成前綴和
    for i in range(1, maxn+1):
        coprime[i] += coprime[i-1]
    # 0 ~ maxn 之間非畢氏三元組的數量前綴和
    non_member = [0]*(maxn+1)
    cnt = 0
    for i in range(1, maxn+1):
        if not status[i]: cnt += 1
        non_member[i] = cnt
    sys.stdout.write(f"{coprime[maxn]:d} {non_member[maxn]:d}\n")

for line in sys.stdin:
    solve(int(line))


2026年3月2日 星期一

ZeroJudge 解題筆記:c096. 00700 - Date Bugs

作者:王一哲
日期:2026年3月2日


ZeroJudge 題目連結:c096. 00700 - Date Bugs

解題想法


我只有想到最暴力的作法,用集合 ans 儲存所有第一部電腦所有可能的年份,再依序計算其它電腦可能的年份存入集合 cand,取 ans 與 cand 的交集更新 ans。如果最後 ans 有資料,答案是 ans 之中的最小值;反之則輸出 **Unknown bugs detected.**。這個寫法不快,但是容易操作。

Python 程式碼


暴力解,使用時間約為 23 ms,記憶體約為 6 MB,通過測試。
import sys

ca = 0
maxy = 10000  # 答案上限為 9999
for line in sys.stdin:
    if line.rstrip() == "0": break;
    ca += 1
    if ca > 1: print()  # 如果不是第一筆測資,先印出空行
    n = int(line)  # 電腦數量
    ans = set()  # 第 1 部電腦可能對應的年份
    yi, ai, bi = map(int, sys.stdin.readline().split())  # 顯示年份 yi,於 bi 年出問題,出問題之後回到 ai
    di = bi - ai  # 從 bi 回到 ai 的差值
    for i in range(0, maxy-yi, di): ans.add(yi+i) # 計算可能的年份差
    for _ in range(n-1):  # 讀取 n-1 行資料
        cand = set()  # 第 2 ~ n 部電腦可能對應的年份
        y, a, b = map(int, sys.stdin.readline().split())  # 顯示年份 y,於 b 年出問題,出問題之後回到 a
        d = b - a  # 從 b 回到 a 的差值
        for i in range(0, maxy-y, d): cand.add(y+i)  # 計算可能的年份差
        ans.intersection_update(cand)  # 更新 ans
    print(f"Case #{ca:d}:")
    if ans:  # 如果有交集,印出 ans 之中的最小值
        print(f"The actual year is {min(ans):d}.")
    else:  # 反之,印出 Unknown bugs detected.
        print("Unknown bugs detected.")


2026年3月1日 星期日

ZeroJudge 解題筆記:c094. 00661 - Blowing Fuses

作者:王一哲
日期:2026年3月1日


ZeroJudge 題目連結:c094. 00661 - Blowing Fuses

解題想法


用陣列記錄每個電器是否開啟,依序讀取操作的電器編號,更新電器使用狀態及總電流。用一個布林值記錄是否已經燒掉,如果總電流過大標記為 True。

Python 程式碼


使用時間約為 7 ms,記憶體約為 2.9 MB,通過測試。
import sys

ca = 0  # 第幾次測試
for line in sys.stdin:
    n, m, c = map(int, line.split())  # n 個電器,m 次操作,最大電流 c
    if n == 0 and m == 0 and c == 0: break  # 中止迴圈的條件
    ca += 1  # ca 加 1
    if ca > 1: print()  # 如果不是第 1 次測試,先印出空白列
    current = [0] + [int(sys.stdin.readline()) for _ in range(n)]  # 讀取 n 個電器的電流,開頭加 0
    blown = False  # 是否已燒掉
    curr, imax = 0, 0  # 目前的電流,電流最大值
    turn_on = [False]*(n+1)  # 電器狀態,預設為關閉
    for _ in range(m):  # 讀取 m 次操作
        idx = int(sys.stdin.readline())  # 電器編號
        if blown: continue  # 如果已燒掉,繼續讀完剩下的操作
        turn_on[idx] = not turn_on[idx]  # 更新電器的狀態
        if turn_on[idx]: curr += current[idx]  # 如果開啟,加上這個電器的電流
        else: curr -= current[idx]  # 如果關閉,減去這個電器的電流
        if curr > c: blown = True  # 如果 curr 大於 c,已燒掉
        else: imax = max(imax, curr)  # 反之,更新最大電流
    print(f"Sequence {ca:d}")  # 印出答案
    if blown:
        print("Fuse was blown.")
    else:
        print("Fuse was not blown.")
        print(f"Maximal power consumption was {imax:d} amperes.")


2026年2月28日 星期六

ZeroJudge 解題筆記:c093. 00608 - Counterfeit Dollar

作者:王一哲
日期:2026年2月28日


ZeroJudge 題目連結:c093. 00608 - Counterfeit Dollar

解題想法


這題的困難之處在於分析測量結果,詳情請參考下方程式碼之中的註解。我是用集合儲存硬幣資料,由於 Python 的集合可以直接取聯集 (union)、差集 (difference),寫起來比較簡單。

Python 程式碼


使用時間約為 7 ms,記憶體約為 3 MB,通過測試。
T = int(input())  # T 組測資
for _ in range(T):
    """ 前置作業 """
    data = []  # 測試資料,內容為 [left, right, satatus]
    coins = set()  # 所有參與測試的硬幣
    status = dict()  # 硬幣狀態,內容為 name: [light possible, heavy possible]
    for _ in range(3):  # 讀取 3 次測試結果
        left, right, state = input().split()  # 左側硬幣,右側硬幣,右側狀態
        data.append([left, right, state])  # 資料加到 data
        for coin in left: coins.add(coin)  # 更新 coins
        for coin in right: coins.add(coin)
    for coin in coins:  # 更新 status,預設值為 [True, True]
        status[coin] = [True, True]
    """ 處理測試資料 """
    for left, right, state in data:  # 依序讀取測試資料
        if state == "even":  # 如果兩側平衡,左、右的硬幣都是真的
            for coin in left: status[coin] = [False, False]  # 更新 status,兩種狀態都是 False
            for coin in right: status[coin] = [False, False]
        elif state == "up":  # 如果右側較高,代表左側較重、右側較輕,假的硬幣一定在這些硬幣之中
            leri = set(left).union(set(right))  # 兩側硬幣的聯集
            for coin in coins.difference(leri):  # 沒有參與這次測試的硬幣都是真的
                status[coin] = [False, False]
            for coin in left: status[coin][0] = False  # 左側硬幣不可能比真的硬幣輕
            for coin in right: status[coin][1] = False  # 右側硬幣不可能比真的硬幣重
        else:  # 如果右側較低,代表左側較輕、右側較重,假的硬幣一定在這些硬幣之中
            leri = set(left).union(set(right))  # 兩側硬幣的聯集
            for coin in coins.difference(leri):  # 沒有參與這次測試的硬幣都是真的
                status[coin] = [False, False]
            for coin in left: status[coin][1] = False  # 左側硬幣不可能比真的硬幣重
            for coin in right: status[coin][0] = False  # 右側硬幣不可能比真的硬幣輕
    """ 結束測試,找答案 """
    fake, result = "", ""  # 假的硬幣名稱、狀態
    for coin, (light, heavy) in status.items():  # 依序讀取 status 的資料
        if light and not heavy:  # 如果這個硬幣可能比較輕、不可能比較重
            fake = coin  # 找到假的硬幣
            result = "light"  # 較輕
            break  # 中止迴圈
        if not light and heavy:  # 如果這個硬幣不可能比較輕、可能比較重
            fake = coin  # 找到假的硬幣
            result = "heavy"  # 較重
            break  # 中止迴圈
    print(f"{fake:s} is the counterfeit coin and it is {result:s}.")


2026年2月27日 星期五

ZeroJudge 解題筆記:c092. 00587 - There's treasure everywhere!

作者:王一哲
日期:2026年2月27日


ZeroJudge 題目連結:c092. 00587 - There's treasure everywhere!

解題想法


我先用字典儲存各種方向縮寫對應的 1 個單位位移量,接下來再依照讀取到的測資計算東西向、南北向位移。我花最多時間的地方反而是在讀取測資,因為用數量不一定的逗號、句號分隔資料,反而比用空格分隔資料還要麻煩。用 C++ 解題時,如果用 float 會吃 WA,要用 double 才能 AC。

Python 程式碼


使用時間約為 7 ms,記憶體約為 2.9 MB,通過測試。
import sys

ca = 0
for line in sys.stdin:
    if line.rstrip() == "END": break
    ca += 1  # case 加 1
    if ca > 1: print()
    x, y = 0.0, 0.0  # 目前的位置
    dia = 1.0/(2**0.5)  # 對角線長度,2分之根號2
    dirs = {"N": (0.0, 1.0), "E": (1.0, 0.0), "S": (0.0, -1.0), "W": (-1.0, 0.0),
            "NE": (dia, dia), "NW": (-dia, dia), "SE": (dia, -dia), "SW": (-dia, -dia)}
    steps = line[:-2].split(",")  # 刪除最後的 .\n,再用 , 分割,存成串列
    for step in steps:  # 依序讀取每一步的距離、方向
        a = 0; d = ""  # 移動距離、方向
        for i, c in enumerate(step):  # 依序讀取 step 的字元,讀到英文字母為止
            if c.isalpha():
                a = int(step[:i])
                d = step[i:]
                break
        x, y = x + a*dirs[d][0], y + a*dirs[d][1]  # 更新位置
    print(f"Map #{ca:d}")
    print(f"The treasure is located at ({x:.3f},{y:.3f}).")
    print(f"The distance to the treasure is {(x*x + y*y)**0.5:.3f}.")


2026年2月26日 星期四

ZeroJudge 解題筆記:c091. 00576 - Hiaku Review

作者:王一哲
日期:2026年2月26日


ZeroJudge 題目連結:c091. 00576 - Hiaku Review

解題想法


Python 可以用 split("/") 以斜線分割句字,而 C++ 則是依序讀取字元,讀到 / 時結算前一個句字。依序計算每個句子中的音節數量,但是連續的母音只能算 1 個音節,記錄前方的字元否為母音,如果這個字元不是母音,則音節數量加 1。

Python 程式碼


使用時間約為 7 ms,記憶體約為 2.8 MB,通過測試。
import sys

for line in sys.stdin:
    if line.rstrip() == "e/o/i": break
    sens = line.rstrip().split("/")
    standard = (0, 5, 7, 5)  # 三個句子的標準音節數量
    haiku = True  # 是否符合規則
    for i, sen in enumerate(sens, start=1):
        cnt = 0  # 音節數量
        state = False  # 前方的字元是否為母音 aeiouy
        for c in sen:  # 依序讀取 sen 的字元 c
            if c in "aeiouy": state = True  # 如果 c 是 aeiouy
            else:  # 如果 c 不是 aeiouy
                if state: cnt += 1  # 如果前方的字元是母音,音節數量加 1
                state = False  # 重設狀態
        if state: cnt += 1  # 如果最後是連續母音,cnt 加 1
        if cnt != standard[i]:  # 如果音節數量不合
            print(i)  # 印出 i
            haiku = False  # 不符合規則
            break  # 中止迴圈
    if haiku: print("Y")  # 如果符合規則,印出 Y


2026年2月25日 星期三

ZeroJudge 解題筆記:c089. 00389 - Basically Speaking

作者:王一哲
日期:2026年2月25日


ZeroJudge 題目連結:c089. 00389 - Basically Speaking

解題想法


由於 Python 的 int 內建了用指定進位制將字串轉換成 10 進位整數的功能,這部分可以使用預設工具;但是輸出格式之中沒有對應 7 進位制的工具,需要自己寫。而 C++ 則是兩個部分都要自己寫。

Python 程式碼


使用時間約為 7 ms,記憶體約為 2.9 MB,通過測試。
import sys

def ntos(n, base):  # 輸入整數 n、基底 base,輸出轉換後的字串
    # 數字 0 ~ 15 對應的字元
    table = {0: '0', 1: '1', 2: '2', 3: '3',
             4: '4', 5: '5', 6: '6', 7: '7',
             8: '8', 9: '9', 10: 'A', 11: 'B',
             12: 'C', 13: 'D', 14: 'E', 15: 'F'}
    s = ""  # 儲存結果用的字串
    while n > 0:  # 如果 n 大於 0 繼續執行
        s = table[n%base] + s  # 取 n%base 對應的字元加到 s 前面
        n //= base  # n 變為 1/base 倍
    return s  # 回傳 s

for line in sys.stdin:
    arr = line.split()  # 分割後會轉為串列存到 arr
    # 先將字串用指定的基底 arr[1] 轉成 10 進位整數,再用 ntos 轉成 arr[2] 進位字串
    s = ntos(int(arr[0], int(arr[1])), int(arr[2]))
    m = len(s)  # s 的長度
    if m > 7: print("  ERROR")  # 長度大於 7,印出  ERROR
    else: print(" "*(7-m) + s)  # 反之前面補空格到長度為 7 再輸出


2026年2月24日 星期二

ZeroJudge 解題筆記:c088. 00516 - Prime Land

作者:王一哲
日期:2026年2月24日


ZeroJudge 題目連結:c088. 00516 - Prime Land

解題想法


讀取一行測資,再每次讀取測資中的兩個數字,分別為底數及次方,將這個數字 $n$ 還原成 10 進位;接下來對 $n-1$ 做質因數分解,依照題目要求的格式輸出答案。

Python 程式碼


使用時間約為 24 ms,記憶體約為 3.1 MB,通過測試。
import sys

for line in sys.stdin:
    if line.rstrip() == "0": break
    arr = list(map(int, line.split()))  # 讀取一行資料
    n, m = 1, len(arr)  # n 儲存測資對應的整數,測資長度 m
    for i in range(0, m, 2):  # 一次讀取測資中的兩個數字
        n *= arr[i]**arr[i+1]  # 計算 n
    n -= 1  # 題目要算 n-1 的質因數分解結果
    curr = n  # 儲存 n-1,之後的計算會改變量值
    ans = []  # 答案
    for i in range(2, int(n**0.5)+1):  # 測試 2 ~ sqrt(n) 的質因數
        p = 0  # i 的次方
        while curr%i == 0:  # 如果 curr 能被 i 整除
            p += 1  # 次方加 1
            curr //= i  # curr 變為 1/i 倍
        if p > 0: ans += [p, i]  # 如果 p 大於 0,ans 加上 [p, i]
    if curr > 1: ans += [1, curr]  # 如果 curr 大於 1,ans 要加上 [1, curr]
    if not ans: print(n, 1)  # 如果 ans 是空的,n 是因數,輸出 n 1
    else: print(*ans[::-1])  # 題目要求因數由大到小輸出


2026年2月23日 星期一

ZeroJudge 解題筆記:c087. 00412 - Pi

作者:王一哲
日期:2026年2月23日


ZeroJudge 題目連結:c087. 00412 - Pi

解題想法


如果測資提供 $n$ 個數字,所有的數對共有 $$ tot = C_n^2 = \frac{n(n-1)}{2} $$ 如果其中有 $cnt$ 組互質,則 $$ \frac{6}{\pi^2} = \frac{cnt}{tot} ~\Rightarrow~ \pi = \sqrt{\frac{6 \times tot}{cnt}} $$

Python 程式碼


用 itertools.combinations 産生數對組合,使用時間約為 7 ms,記憶體約為 2.9 MB,通過測試。
import sys, itertools, math

for line in sys.stdin:
    n = int(line)
    if n == 0: break
    nums = [int(sys.stdin.readline()) for _ in range(n)]
    cnt, tot = 0, n*(n-1)//2
    for a, b in itertools.combinations(nums, 2):
        if math.gcd(a, b) == 1: cnt += 1
    if cnt == 0:
        print("No estimate for this data set.")
    else:
        print(f"{(6*tot/cnt)**0.5:.6f}")

用遞迴枚舉所有的數對組合,使用時間約為 7 ms,記憶體約為 2.9 MB,通過測試。
import sys, math

def comb(nums, arr, cnt):
    if len(arr) == 2:
        if math.gcd(*arr) == 1: return cnt+1
        return cnt
    for i in range(len(nums)):
        arr.append(nums[i])
        cnt = comb(nums[i+1:], arr, cnt)
        arr.pop()
    return cnt

for line in sys.stdin:
    n = int(line)
    if n == 0: break
    nums = [int(sys.stdin.readline()) for _ in range(n)]
    tot = n*(n-1)//2
    cnt = comb(nums, [], 0)
    if cnt == 0:
        print("No estimate for this data set.")
    else:
        print(f"{(6*tot/cnt)**0.5:.6f}")


2026年2月22日 星期日

ZeroJudge 解題筆記:c086. 00402 - M*A*S*H

作者:王一哲
日期:2026年2月22日


ZeroJudge 題目連結:c086. 00402 - M*A*S*H

解題想法


很像約瑟夫環的題目,但是測資不大,直接模擬淘汰的過程即可。

Python 程式碼


使用時間約為 7 ms,記憶體約為 3 MB,通過測試。
import sys

ca = 0  # 第幾筆測資
for line in sys.stdin:
    ca += 1  # 加1
    if ca > 1: print()  # 如果不是第1筆測資,換行
    arr = list(map(int, line.split()))  # 讀取一行資料
    n, x = arr[0], arr[1]  # n 個人參加,x 個人可以放假
    persons = [i for i in range(1, n+1)]  # 産生 1 ~ n 的編號
    for k in arr[2:]:  # 依序讀取 20 張牌的數字
        if n == x: break  # 如果剩下 x 個人,中止迴圈
        idx = k-1  # 淘汰者的索引值,初始值為 k-1
        while n > x and idx < n:  # 如果人數大於 x 而且 idx 還沒有出界
            persons.pop(idx)  # 淘汰索引值為 idx 的人
            n -= 1  # 人數減 1
            idx += k-1  # 下一個索引值加 k-1,因為淘汰一個人,不是加 k
    print(f"Selection #{ca:d}")  # 印出答案
    print(*persons)

使用時間約為 8 ms,記憶體約為 3 MB,通過測試。
import sys

result = []
data = sys.stdin.read().split()
ptr = 0
ca = 0  # 第幾筆測資
while ptr < len(data):
    ca += 1  # 加1
    if ca > 1: result.append("\n")  # 如果不是第1筆測資,換行
    n = int(data[ptr])  # n 個人參加
    x = int(data[ptr+1])  # x 個人可以放假
    ptr += 2
    persons = list(range(1, n+1))  # 産生 1 ~ n 的編號
    cards = list(map(int, data[ptr:ptr+20]))
    ptr += 20
    for k in cards:  # 依序讀取 20 張牌的數字
        if n == x: break  # 如果剩下 x 個人,中止迴圈
        idx = k-1  # 淘汰者的索引值,初始值為 k-1
        while n > x and idx < n:  # 如果人數大於 x 而且 idx 還沒有出界
            persons.pop(idx)  # 淘汰索引值為 idx 的人
            n -= 1  # 人數減 1
            idx += k-1  # 下一個索引值加 k-1,因為淘汰一個人,不是加 k
    result.append(f"Selection #{ca:d}\n")  # 印出答案
    res = " ".join(map(str, persons))
    result.append(f"{res}\n")
sys.stdout.write("".join(result))


2026年2月21日 星期六

ZeroJudge 解題筆記:c084. 00275 - Expanding Fractions

作者:王一哲
日期:2026年2月21日


ZeroJudge 題目連結:c084. 00275 - Expanding Fractions

解題想法


這題要依序處理每個位數相除的結果,如果無法整除時要將餘數乘以 10 再繼續往下除,同時記錄每個餘出現過的位置,直到先找循環節的終點為止。最後還要依照題目要求的格式輸出答案。

Python 程式碼


使用時間約為 9 ms,記憶體約為 3.1 MB,通過測試。
import sys

def divid(a, b):  # a < b, 計算 a/b
    dec_part = []  # 小數部分
    repeat_start = -1  # 循環小數起點
    rem = a  # 餘數,由於題目限制 a < b,初始值為 a
    rem_pos = dict()  # 用字典儲存出現過的餘數位置
    while rem != 0:  # 如果無法整除繼續執行
        if rem in rem_pos:  # 如果這個餘數已經出現過
            repeat_start = rem_pos[rem]  # 設定循環的起點
            break  # 中止迴圈
        rem_pos[rem] = len(dec_part)  # 記錄這個餘數的位置
        rem *= 10  # 將餘數放大為 10 倍
        dec_part.append(str(rem//b))  # 小數部分加一位
        rem %= b  # 新的餘數
    ans = "." + "".join(dec_part)  # 要輸出的答案
    for i in range(0, len(ans), 50):  # 逐行輸出,每行最多 50 個字元
        print(ans[i:i+50])
    if repeat_start == -1:  # 不循環
        print("This expansion terminates.")
    else:  # 循環
        print(f"The last {len(dec_part)-repeat_start:d} digits repeat forever.")

for line in sys.stdin:
    n, m = map(int, line.split())
    if n == 0 and m == 0: break
    divid(n, m)

如果題目改成要計算相除的小數並標示循環節,可以這樣寫。
import sys

def divid(a, b):  # a < b, 計算 a/b
    int_part = str(a//b)  # 整數部分
    rem = a%b  # 餘數
    """ 特例,可以整除,回傳整數部分 """
    if rem == 0: return int_part
    """ 繼續處理小數部分 """
    dec_part = []  # 小數部分
    repeat_start = -1  # 循環小數起點
    rem_pos = dict()  # 用字典儲存出現過的餘數位置
    while rem != 0:  # 如果無法整除繼續執行
        if rem in rem_pos:  # 如果這個餘數已經出現過
            repeat_start = rem_pos[rem]  # 設定循環的起點
            break  # 中止迴圈
        rem_pos[rem] = len(dec_part)  # 記錄這個餘數的位置
        rem *= 10  # 將餘數放大為 10 倍
        dec_part.append(str(rem//b))  # 小數部分加一位
        rem %= b  # 新的餘數
    if repeat_start == -1:  # 不循環
        return int_part + "." + "".join(dec_part)
    else:  # 循環,用 () 標示循環節
        return int_part + "." + "".join(dec_part[:repeat_start]) \
               + "(" + "".join(dec_part[repeat_start:]) + ")"

for line in sys.stdin:
    n, m = map(int, line.split())
    if n == 0 and m == 0: break
    print(divid(n, m))


2026年2月20日 星期五

ZeroJudge 解題筆記:c083. 00130 - Roman Roulette

作者:王一哲
日期:2026年2月20日


ZeroJudge 題目連結:c083. 00130 - Roman Roulette

解題想法


這題的題目敘述很怪,試了很多種排法才弄懂題目的意思。為了簡化問題,我將人排成一列,順時鐘方向是向右,如果到了最後一位就從最左邊開始算。計算規則如下:
  1. 首先是題目要找的起點位置,在計算第一個受害者的位置時,是從起點的左邊那個人開始向右算 k 個人,不是從起點開始算。
  2. 計算埋葬者原來的位置時,是從受害者左邊那個人開始向右算 k 個人,將埋葬者的位置移動到受害者的位置。
  3. 計算下一個受害者的位置時,是從前一個受害者,也就是埋葬者所在位置左邊那個人開始向右算 k 個人。
  4. 如果 1 號死亡,可以中止迴圈,再找下一個起點。
  5. 如果存活人數剩下一個,而且這個人是 1 號,找到答案,回傳起點位置。
以範例測資 $n = 5, k = 2, i = 1$ 為例,起始及每一輪的狀態如下
起始: [1, 2, 3, 4, 5]
第1輪: 起點編號 5, 受害者編號 2, 埋葬者編號 4, 目前存活的人 [1, 4, 3, 5]
第2輪: 起點編號 4, 受害者編號 5, 埋葬者編號 4, 目前存活的人 [1,  3, 4]
第3輪: 起點編號 4, 受害者編號 3, 埋葬者編號 1, 目前存活的人 [1, 4]
第4輪: 起點編號 1, 受害者編號 1, 目前存活的人 [4]


Python 程式碼


使用時間約為 8 ms,記憶體約為 2.9 MB,通過測試。
import sys

def solve(n, k):  # 用串列模擬過程求解
    for i in range(1, n+1):  # 依序測試起點編號 1 ~ n
        persons = [j for j in range(1, n+1)]  # 編號 1 ~ n 的人
        pos, m = (i-2+n)%n, n  # 起點位置其實是 i 的左側,轉成索引值再減 1;存活人數 m
        alive = True  # 1 號是否存活
        while m > 1 and alive:  # 如果 m 大於 1 且 1 號還活著,繼續執行
            vpos = (pos+k)%m  # 受害者索引值
            victim = persons.pop(vpos)  # 從 persons 移除受害者
            m -= 1  # 存活人數減 1
            if victim == 1: alive = False  # 如果 1 號死亡,alive 改成 False
            if m == 1: break  # 如果 m 等於 1,中止迴圈
            bpos = (vpos-1+k)%m  # 埋葬者索引值
            burier = persons.pop(bpos)  # 從 persons 移除埋葬者
            if bpos < vpos:  # 如果埋葬者在受害者左側
                persons.insert(vpos-1, burier)  # vpos 減 1,於 persons 插入埋葬者
                pos = (vpos-1+m)%m  # 更新下一輪的起點索引值
            else:  # 如果埋葬者在受害者右側
                persons.insert(vpos, burier)  # 於 persons 插入埋葬者
                pos = (vpos+m)%m  # 更新下一輪的起點索引值
        if alive: return i  # 如果 1 號存活,回傳 i
    return -1  # 無解,回傳 -1,理論上不會用到

for line in sys.stdin:
    n, k = map(int, line.split())
    if n == 0 and k == 0: break
    print(solve(n, k))


2026年2月19日 星期四

ZeroJudge 解題筆記:c082. 00118 - Mutant Flatworld Expolrers

作者:王一哲
日期:2026年2月19日


ZeroJudge 題目連結:c082. 00118 - Mutant Flatworld Expolrers

解題想法


這類走格子的題目,我通常會在地圖的周圍加上一圈地圖中原來沒有使用的字元當作邊界,如果走到的格子是這個字元就代表出界,這樣可以不需要檢查索引值的邊界值。
依照題目給的指令,依序更新位置及方向,如果碰掉邊界標示為已掉落,不需要執行之後的指令。如果指令全部執行完還沒有掉落,印出最後的位置。

Python 程式碼


使用時間約為 7 ms,記憶體約為 3 MB ,通過測試。
import sys

ctod = {'N': 0, 'E': 1, 'S': 2, 'W': 3}
dtoc = ('N', 'E', 'S', 'W')
dx = (0, 1, 0, -1)  # 向東 +x,順序為 NESW
dy = (1, 0, -1, 0)  # 向北 +y,順序為 NESW

n, m = map(int, sys.stdin.readline().split())  # 地圖右上角頂點 (n, m)
# 地圖最右側、最上方加 -1 作為哨兵,0 為未標記,1 為已標記
# grid[y][x] 對應到地圖座標 (x, y)
grid = [[0]*(n+1) + [-1] for _ in range(m+1)]
grid.append([-1]*(n+2))

for line in sys.stdin:
    cmd = list(line.split())
    x, y, d = int(cmd[0]), int(cmd[1]), ctod[cmd[2]]  # 起點座標 (x, y),方向 d
    lost = False  # 是否掉落
    for c in sys.stdin.readline().rstrip():
        if c == 'R': d = (d+1)%4  # 向右轉90度
        elif c == 'L': d = (d-1+4)%4  # 向左轉90度
        else:  # 前進1格
            nx, ny = x+dx[d], y+dy[d]  # 測試 (ny, nx)
            if grid[ny][nx] == -1:  # (ny, nx) 出界
                if grid[y][x] == 0:  # (y, x) 未標記
                    print(f"{x:d} {y:d} {dtoc[d]:s} LOST")  # 最後的位置
                    grid[y][x] = 1  # 標記 (x, y)
                    lost = True  # 已掉落
                    break  # 中止迴圈,忽略之後的指令
                else: continue  # (y, x) 已標記,執行下一個指令
            else:  # (ny, nx) 未出界
                x, y = nx, ny  # 更新 x, y
    # 執行完指令,未掉落,印出位置及方向
    if not lost: print(f"{x:d} {y:d} {dtoc[d]:s}")