熱門文章

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}")


2026年2月18日 星期三

ZeroJudge 解題筆記:c081. 00102 - Ecological Bin Packing

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


ZeroJudge 題目連結:c081. 00102 - Ecological Bin Packing

解題想法


只有 6 種排列方式,分別為 BCG, BGC, CBG, CGB, GBC, GCB,依照這個順序分別計算對應的移動數量,取第一個移動數量最小值。

Python 程式碼


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

for line in sys.stdin:
    b1, g1, c1, b2, g2, c2, b3, g3, c3 = map(int, line.split())
    # 測試的排列順序為 BCG, BGC, CBG, CGB, GBC, GCB
    ans, imin = "", 1E9
    cnt1 = g1+c1+b2+g2+b3+c3
    if cnt1 < imin:
        ans = "BCG"; imin = cnt1
    cnt2 = g1+c1+b2+c2+b3+g3
    if cnt2 < imin:
        ans = "BGC"; imin = cnt2
    cnt3 = b1+g1+g2+c2+b3+c3
    if cnt3 < imin:
        ans = "CBG"; imin = cnt3
    cnt4 = b1+g1+b2+c2+g3+c3
    if cnt4 < imin:
        ans = "CGB"; imin = cnt4
    cnt5 = b1+c1+g2+c2+b3+g3
    if cnt5 < imin:
        ans = "GBC"; imin = cnt5
    cnt6 = b1+c1+b2+g2+g3+c3
    if cnt6 < imin:
        ans = "GCB"; imin = cnt6
    print(ans, imin)

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

for line in sys.stdin:
    b1, g1, c1, b2, g2, c2, b3, g3, c3 = map(int, line.split())
    # BCG
    cnt1 = g1+c1+b2+g2+b3+c3
    # BGC
    cnt2 = g1+c1+b2+c2+b3+g3
    # CBG
    cnt3 = b1+g1+g2+c2+b3+c3
    # CGB
    cnt4 = b1+g1+b2+c2+g3+c3
    # GBC
    cnt5 = b1+c1+g2+c2+b3+g3 
    # GCB
    cnt6 = b1+c1+b2+g2+g3+c3
    ans = sorted([(cnt1, "BCG"), (cnt2, "BGC"), (cnt3, "CBG"), (cnt4, "CGB"), (cnt5, "GBC"), (cnt6, "GCB")])
    print(ans[0][1], ans[0][0])


2026年2月17日 星期二

ZeroJudge 解題筆記:c077. 00417 - Word Index

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


ZeroJudge 題目連結:c077. 00417 - Word Index

解題想法


這題給的條件比較寬鬆,先建表列出所有答案再查表也能通過。但如果能夠找出規律,用數學解會快很多。

Python 程式碼


建表,使用時間約為 31 ms,記憶體約為 15.6 MB,通過測試。
import sys

cnt = 0  # 編號
table = dict()  # 表格,內容為{字串: 編號}
# a ~ z 的編號
for i in range(26):
    cnt += 1
    s = chr(i+ord('a'))
    table[s] = cnt
# ab ~ yz 的編號
for i in range(26):
    s = chr(i+ord('a'))
    for j in range(i+1, 26):
        t = s + chr(j+ord('a'))
        cnt += 1
        table[t] = cnt
# abc ~ xyz 的編號
for i in range(26):
    s = chr(i+ord('a'))
    for j in range(i+1, 26):
        t = s + chr(j+ord('a'))
        for k in range(j+1, 26):
            u = t + chr(k+ord('a'))
            cnt += 1
            table[u] = cnt
# abcd ~ wxyz 的編號
for i in range(26):
    s = chr(i+ord('a'))
    for j in range(i+1, 26):
        t = s + chr(j+ord('a'))
        for k in range(j+1, 26):
            u = t + chr(k+ord('a'))
            for x in range(k+1, 26):
                v = u + chr(x+ord('a'))
                cnt += 1
                table[v] = cnt
# abcde ~ vwxyz 的編號
for i in range(26):
    s = chr(i+ord('a'))
    for j in range(i+1, 26):
        t = s + chr(j+ord('a'))
        for k in range(j+1, 26):
            u = t + chr(k+ord('a'))
            for x in range(k+1, 26):
                v = u + chr(x+ord('a'))
                for y in range(x+1, 26):
                    w = v + chr(y+ord('a'))
                    cnt += 1
                    table[w] = cnt
# 讀取測資並查表輸出答案,如果 key 不在 table 之中則輸出 0
for line in sys.stdin:
    print(table.get(line.rstrip(), 0))

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

def comb(n, m):  # C n 取 m
    if m < 0 or m > n: return 0  # 不合法的算式,回傳 0
    m = min(m, n-m)  # 取 m, n-m 較小者
    ans = 1  # 組合數
    for i in range(1, m+1):
        ans = ans*(n-i+1)//i
    return ans

def calculate_code(s):  # 輸入字串,回傳編號
    n, code = len(s), 0  # 字串長度,編號
    # 檢查字符串是否合法,後面的字元大於前面的字元
    for i in range(n-1):
        if s[i+1] <= s[i]:  # 不合法,回傳 0
            return 0
    # 計算長度為 1 ~ n-1 的組合數
    for i in range(1, n):
        code += comb(26, i)
    # 計算長度 n 的排名
    for i in range(n):  # 掃過 s[0] ~ s[n-i]
        start = ord('a') if i == 0 else ord(s[i-1]) + 1  # i = 0 從 a 開始,否則從 s[i-1] 下一個開始
        end = ord(s[i])  # 到 s[i] 的編號為止
        for c in range(start, end):
            code += comb(26 - (c-ord('a')+1), n-i-1)
    # 最後要再加 1
    return code + 1

for line in sys.stdin:
    print(calculate_code(line.rstrip()))


2026年2月16日 星期一

ZeroJudge 解題筆記:c074. 00441 - Lotto

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


ZeroJudge 題目連結:c074. 00441 - Lotto

解題想法


這題可以用遞迴窮舉所有的組合。但如果用 Python 解題,可以用 itertools.combinations 枚舉所有指定數據中取出 6 個的組合,速度比遞迴還快。

Python 程式碼


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

def solve(cand, arr, depth):
    if len(arr) == depth:
        print(*arr)
        return
    for i in range(len(cand)):
        arr.append(cand[i])
        solve(cand[i+1:], arr, depth)
        arr.pop()
        
first = True
for line in sys.stdin:
    if line.rstrip() == "0": break
    if not first: print()
    first = False
    nums = list(map(int, line.split()))[1:]
    solve(nums, [], 6)

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

result = []
data = sys.stdin.read().split()
ptr = 0
while ptr < len(data):
    n = int(data[ptr])
    ptr += 1
    if n == 0: break
    nums = sorted(map(int, data[ptr:ptr+n]))
    ptr += n
    if result: result.append("\n")
    for comb in combinations(nums, 6):
        res = " ".join(map(str, comb))
        result.append(f"{res}\n")
sys.stdout.write("".join(result))


2026年2月15日 星期日

ZeroJudge 解題筆記:c073. 00101 - The Blocks Problem

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


ZeroJudge 題目連結:c073. 00101 - The Blocks Problem

解題想法


指令有以下 5 種,其中 a 和 b 是積木的編號:
  1. move a onto b: 先將 a 和 b 上方的積木放回原來的位置,再將 a 移到 b 上方。
  2. move a over b: 先將 a 上方的積木放回原來的位罝,b 所在的那堆積木不動,再將 a 移到 b 上方。
  3. pile a onto b: 先將 b 上方的積木放回原位,再將 a 本身及上方的積木一起放到 b 上方。
  4. pile a over b: 將 a 本身和上方的積木一起搬到到 b 所在的那堆積木上方。
  5. quit: 動作結束,印出結果。
先畫出範例測資 1 的操作過程再寫程式碼,會比較清楚要怎麼寫。範例測資 1,$n = 10$,初始及每次操作後的狀態分別為
initial: [[0], [1], [2], [3], [4], [5], [6], [7], [8], [9]]
move 9 onto 1: [[0], [1, 9], [2], [3], [4], [5], [6], [7], [8], []]
move 8 over 1: [[0], [1, 9, 8], [2], [3], [4], [5], [6], [7], [], []]
move 7 over 1: [[0], [1, 9, 8, 7], [2], [3], [4], [5], [6], [], [], []]
move 6 over 1: [[0], [1, 9, 8, 7, 6], [2], [3], [4], [5], [], [], [], []]
pile 8 over 6: [[0], [1, 9, 8, 7, 6], [2], [3], [4], [5], [], [], [], []]  # 8, 6 在同一疊,無效
pile 8 over 5: [[0], [1, 9], [2], [3], [4], [5, 8, 7, 6], [], [], [], []]
move 2 over 1: [[0], [1, 9, 2], [], [3], [4], [5, 8, 7, 6], [], [], [], []]
move 4 over 9: [[0], [1, 9, 2, 4], [], [3], [], [5, 8, 7, 6], [], [], [], []]

範例測資 2,$n = 4$,初始及每次操作後的狀態分別為
initial: [[0], [1], [2], [3]]
pile 0 over 1: [[], [1, 0], [2], [3]]
pile 2 over 3: [[], [1, 0], [], [3, 2]]
move 1 onto 3: [[0], [], [2], [3, 1]]


Python 程式碼


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

for line in sys.stdin:
    n = int(line)
    piles = [[i] for i in range(n)]  # 每一疊的木塊編號
    pos = [i for i in range(n)]  # 每個編號的木塊各在第幾疊
    for line in sys.stdin:
        if line.rstrip() == "quit":
            for i, pile in enumerate(piles):
                print(f"{i:d}: ", end="")
                print(*pile)
            break
        command = list(line.split())
        a, b = int(command[1]), int(command[3])
        if pos[a] == pos[b]: continue  # a, b 在同一疊,不合法的指令
        if command[0] == "move":
            if command[2] == "onto":
                while piles[pos[a]][-1] != a:  # 檢查 a 所在的那一疊上方
                    c = piles[pos[a]].pop()  # 取出並移除最上方的木塊編號為 c
                    pos[c] = c  # 把 c 歸位
                    piles[c].append(c)
                while piles[pos[b]][-1] != b:  # 檢查 b 所在的那一疊上方
                    c = piles[pos[b]].pop()  # 取出並移除最上方的木塊編號為 c
                    pos[c] = c  # 把 c 歸位
                    piles[c].append(c)
                piles[pos[b]].append(piles[pos[a]].pop())  # 將 a 移到 b 所在那一疊上方
                pos[a] = pos[b]  # a 的位置改成 b 所在的位置
            elif command[2] == "over":
                while piles[pos[a]][-1] != a:  # 檢查 a 所在的那一疊上方
                    c = piles[pos[a]].pop()  # 取出並移除最上方的木塊編號為 c
                    pos[c] = c  # 把 c 歸位
                    piles[c].append(c)
                piles[pos[b]].append(piles[pos[a]].pop())  # 將 a 移到 b 所在那一疊上方
                pos[a] = pos[b]  # a 的位置改成 b 所在的位置
        elif command[0] == "pile":
            if command[2] == "onto":
                while piles[pos[b]][-1] != b:  # 檢查 b 所在的那一疊上方
                    c = piles[pos[b]].pop()  # 取出並移除最上方的木塊編號為 c
                    pos[c] = c  # 把 c 歸位
                idx = piles[pos[a]].index(a)  # 找出 a 在這一疊的索引值
                sub = piles[pos[a]][idx:]  # 取出 idx 之後的項目
                piles[pos[a]] = piles[pos[a]][:idx]  # 移除 idx 之後的項目
                for d in sub:  # 修改 a 及上方積木的位置
                    pos[d] = pos[b]  # 改成 b 所在的位置
                piles[pos[b]] += sub  # 將 a 及上方的積木移到 b 所在那一疊上方
            elif command[2] == "over":
                idx = piles[pos[a]].index(a)  # 找出 a 在這一疊的索引值
                sub = piles[pos[a]][idx:]  # 取出 idx 之後的項目
                piles[pos[a]] = piles[pos[a]][:idx]  # 移除 idx 之後的項目
                for d in sub:  # 修改 a 及上方積木的位置
                    pos[d] = pos[b]  # 改成 b 所在的位置
                piles[pos[b]] += sub  # 將 a 及上方的積木移到 b 所在那一疊上方


2026年2月14日 星期六

ZeroJudge 解題筆記:c069. 00602 - What Day Is It?

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


ZeroJudge 題目連結:c069. 00602 - What Day Is It?

解題想法


將題目需要的功能分拆成以下的自訂函式
  1. is_leap_julian,用來判斷依據儒略曆閏年規則,輸入的年份是否為閏年。
  2. is_leap_gregorian,用來判斷依據格里曆閏年規則,輸入的年份是否為閏年。
  3. is_valid_date,檢查日期是否有效。
  4. get_day_of_week,計算指定日期是星期幾。


Python 程式碼


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

# 月份名稱對照表
month_names = ("", "January", "February", "March", "April", "May", "June",
               "July", "August", "September", "October", "November", "December")
# 每個月的天數 (非閏年)
month_days = (0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)
# 星期名稱
weekday_names = ("Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday")

def is_leap_julian(year):
    """ 儒略曆閏年規則:能被4整除 """
    return year % 4 == 0

def is_leap_gregorian(year):
    """ 格里曆閏年規則:能被4整除且不被100整除,或者能被400整除 """
    return (year % 4 == 0 and year % 100 != 0) or (year % 400 == 0)

def is_valid_date(month, day, year):
    """ 檢查日期是否有效 """
    # 月份小於 1 或大於 12,日期小於 1,年小於 1
    if month < 1 or month > 12 or day < 1 or year < 1: return False
    # 特殊情況:1752年9月的轉換,沒有9月3日 ~ 13日
    if year == 1752 and month == 9:
        if day == 2 or day == 14: return True
        if 2 < day < 14: return False
    # 該月份的天數
    max_day = month_days[month]
    if month == 2:  # 2月,是否為閏年
        if year < 1752:  # 採用儒略曆
            leap = is_leap_julian(year)
        else:  # 採用格里曆
            leap = is_leap_gregorian(year)
        if leap:  # 如果是閏年,有29天
            max_day = 29
    # 如果 day 小於等於 max_day 為 True,反之為 False
    return day <= max_day

def get_day_of_week(month, day, year):
    """計算指定日期是星期幾"""
    # 題目給定公元1年1月1日是星期六 (Sunday ~ Saturday 的編號分別為 0 ~ 6)
    total_days = 0  # 指定日期為公元1年1月1日之後第幾天
    # 計算從1年到(year-1)年的總天數
    for y in range(1, year):
        if y < 1752:
            leap = is_leap_julian(y)
        else:
            leap = is_leap_gregorian(y)
        total_days += 366 if leap else 365
    # 計算當年1月1日到當月1日的天數
    for m in range(1, month):
        if m == 2:
            if year < 1752:
                leap = is_leap_julian(year)
            else:
                leap = is_leap_gregorian(year)
            total_days += 29 if leap else 28
        else:
            total_days += month_days[m]
    # 加上當月的天數
    total_days += day - 1
    # 特殊調整:1752年9月轉換,橫跨1752年9月14日,要減11天
    if year > 1752 or (year == 1752 and month > 9) or (year == 1752 and month == 9 and day >= 14):
        total_days -= 11
    # 計算星期幾 (1年1月1日是星期六,即weekday=6)
    weekday = (total_days + 6) % 7
    return weekday

""" 主要的解題過程 """
for line in sys.stdin:
    parts = line.strip().split()
    if len(parts) != 3: continue  # 不合法的資料,理論上不會遇到
    month, day, year = map(int, parts)  # 月、日、年轉成整數
    if month == 0 and day == 0 and year == 0: break  # 中止迴圈的條件
    # 檢查日期是否有效
    if not is_valid_date(month, day, year):
        print(f"{month}/{day}/{year} is an invalid date.")
        continue
    # 獲取星期幾
    print(f"{month_names[month]} {day}, {year} is a {weekday_names[get_day_of_week(month, day, year)]}")


2026年2月13日 星期五

ZeroJudge 解題筆記:c061. 00530 - Binomial Showdown

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


ZeroJudge 題目連結:c061. 00530 - Binomial Showdown

解題想法


要用計算階乘的對稱性減少計算次數。

Python 程式碼


因為 ZeroJudge 網站大約在2025年10月更新到 Python 3.8.10,可以使用 math.comb 處理這題。使用時間約為 7 ms,記憶體約為 2.8 MB,通過測試。
import sys, math

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

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

for line in sys.stdin:
    n, m = map(int, line.split())
    if n == 0 and m == 0: break
    ans = 1
    m = min(m, n-m)
    for i in range(1, m+1): ans = ans*(n-m+i)//i
    print(ans)


2026年2月12日 星期四

ZeroJudge 解題筆記:c060. 00392 - Polynomial Showdown

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


ZeroJudge 題目連結:c060. 00392 - Polynomial Showdown

解題想法


這題要考慮各種顯示的格式,很麻煩。

Python 程式碼


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

# 各次方要顯示的變數格式
orders = ("", "x", "x^2", "x^3", "x^4", "x^5", "x^6", "x^7", "x^8")

for line in sys.stdin:
    cofs = list(map(int, line.split()))  # 8 ~ 0 次方的係數
    if all(cof == 0 for cof in cofs):  # 特例,如果所有的係數都是 0
        print("0")  # 印出 0
        continue  # 找下一行
    """ 處理一般的狀況 """
    res = []  # 儲存結果用的串列
    for i, cof in enumerate(cofs):  # 依序讀取係數
        p = 8-i  # 次方
        if cof == 0: continue  # 係數是 0,找下一位
        if cof > 1:  # 係數大於 1
            if res:  # 如果 res 已經有內容,前面要有 " + "
                res.append(f" + {cof:d}{orders[p]:s}")
            else:  # 反之,前面不需要有 " + ",只要有係數、變數、次方
                res.append(f"{cof:d}{orders[p]:s}")
        elif cof == 1:  # 係數等於 1
            if res:  # 如果 res 已經有內容,前面要有 " + "
                if p == 0: res.append(" + 1")  # 常數項,只要有 " + 1"
                else: res.append(f" + {orders[p]:s}")  # 其它項,只要有 " + 變數、次方"
            else:  # 反之,前面不需要有 " + "
                if p == 0: res.append("1")  # 常數項,只要有 "1"
                else: res.append(f"{orders[p]:s}")  # 其它項,只要有變數、次方
        elif cof == -1:  # 係數等於 -1
            if res:  # 如果 res 已經有內容,前面要有 " - "
                if p == 0 res.append(" - 1")  # 常數項,只要有 " - 1"
                else: res.append(f" - {orders[p]:s}")  # 其它項,只要有 " - 變數、次方"
            else:  # 反之,前面不需要有 " - "
                if p == 0: res.append("-1")  # 常數項,只要有 "-1"
                else: res.append(f"-{orders[p]:s}")  # 其它項,只要有 "-變數、次方"
        elif cof < -1:  # 係數小於 -1
            if res:  # 如果 res 已經有內容,前面要有 " - "
                res.append(f" - {-cof:d}{orders[p]:s}")
            else:  # 反之,前面不需要有 " - ",只要有係數、變數、次方
                res.append(f"{cof:d}{orders[p]:s}")
    print("".join(res))  # 將 res 接成字串再輸出


2026年2月11日 星期三

ZeroJudge 解題筆記:c054. 10082 - WERTYU

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


ZeroJudge 題目連結:c054. 10082 - WERTYU

解題想法


因為轉換的關係沒有編碼上的規律,用字典建表比較方便。

Python 程式碼


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

table = {'1': '`', '2': '1', '3': '2', '4': '3', '5': '4', '6': '5',
         '7': '6', '8': '7', '9': '8', '0': '9', '-': '0', '=': '-',
         'W': 'Q', 'E': 'W', 'R': 'E', 'T': 'R', 'Y': 'T', 'U': 'Y',
         'I': 'U', 'O': 'I', 'P': 'O', '[': 'P', ']': '[', '\\': ']',
         'S': 'A', 'D': 'S', 'F': 'D', 'G': 'F', 'H': 'G', 'J': 'H',
         'K': 'J', 'L': 'K', ';': 'L', '\'': ';',
         'X': 'Z', 'C': 'X', 'V': 'C', 'B': 'V', 'N': 'B',
         'M': 'N', ',': 'M', '.': ',', '/': '.', ' ': ' '}

for line in sys.stdin:
    s = line.rstrip()
    t = []
    for c in s: t.append(table.get(c, ' '))
    print("".join(t))


2026年2月10日 星期二

ZeroJudge 解題筆記:c050. 00453 - Goldbach's Conjecture

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


ZeroJudge 題目連結:c050. 00453 - Goldbach's Conjecture

解題想法


先建立 1000000 以內的質數表 primes,但是不包含 2,因為題目要測試的數字大於 4。如果要測試的數字為 $n$,先用二分搜尋法從表格中找到最接近且大於等於 $n$ 的質數索引值 $idx$,接下來從 3 開始往上找到 $primes[idx]$ 為止,最早找到的解就是差值最大的解。

Python 程式碼


使用時間約為 1 s,記憶體約為 25.5 MB,通過測試。
import sys, bisect

### 建立 1 ~ 1000000 的質數表,不包含 2 ###
maxn = 1000000
sieve = [True]*(maxn+1)
sieve[0] = sieve[1] = False
for i in range(0, maxn+1, 2): sieve[i] = False
for i in range(3, int(maxn**0.5) + 1):
    if sieve[i]:
        for j in range(i*i, maxn+1, i):
            sieve[j] = False
primes = [i for i in range(maxn+1) if sieve[i]]

### 主要的解題過程 ###
result = []
data = sys.stdin.read().split()
ptr = 0
while ptr < len(data):
    n = int(data[ptr])
    ptr += 1
    if n == 0: break
    idx = bisect.bisect_left(primes, n)
    a, b = 0, 0
    for x in primes[:idx+1]:  # 從 3 開始往上找,最早找到的解就是差值最大的解
        y = n-x
        idy = bisect.bisect_left(primes, y)
        if primes[idy] == y:
            a, b = x, y
            break
    if a == 0 and b == 0:
        reesult.append("Goldbach's conjecture is wrong.\n")
    else:
        result.append(f"{n:d} = {a:d} + {b:d}\n")
sys.stdout.write("".join(result))


2026年2月9日 星期一

ZeroJudge 解題筆記:c049. 00356 - Square Pegs And Round Holes

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


ZeroJudge 題目連結:c049. 00356 - Square Pegs And Round Holes

解題想法


先算數學,如果正方形的邊長為 $n$,則圓的半徑為 $r = 0.5 \times (2n - 1)$。接下來用兩層 for 迴圈掃過正方形的右上角每一格,假設正方形正中央為原點 $(0, 0$),格子座標的左下角 $(x, y)$,如果 $(x+1)^2 + (y+1)^2 \leq r^2$ 代表這格在圓之中,如果 $x^2 + y^2 > r^2$ 代表這格在圓之外,再將計算結果乘以 4 就是對應的答案 $inner$ 及 $outer$,圓上的格子數則是 $4n^2 - outer - inner$。

Python 程式碼


使用時間約為 1.2 s,記憶體約為 3.3 MB,通過測試。
import sys

first = True
for line in sys.stdin:
    if not first: print()
    first = False
    n = int(line)
    r = (2*n - 1) * 0.5
    inner, outer = 0, 0
    for x in range(n):
        for y in range(n):
            if (x+1)**2 + (y+1)**2 <= r*r: inner += 1
            elif x**2 + y**2 > r*r: outer += 1
    inner *= 4; outer *= 4
    print(f"In the case n = {n:d}, {4*n*n-outer-inner:d} cells contain segments of the circle.")
    print(f"There are {inner:d} cells completely contained in the circle.")

2026年2月8日 星期日

ZeroJudge 解題筆記:c048. 10161 - Ant on a Chessboard

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


ZeroJudge 題目連結:c048. 10161 - Ant on a Chessboard

解題想法


觀察位置的規律,第 1 格在左下角,第 4 格在邊長 $2 \times 2$ 正方形右下角,第 9 格在邊長 $3 \times 3$ 正方形左上角,第 16 格在邊長 $4 \times 4$ 正方形右下角,第 25 格在邊長 $5 \times 5$ 正方形左下角,第 36 格在邊長 $6 \times 6$ 正方形右下角。可以先建表,再用二分搜尋法找 n 在第幾列。也可以直接開根號,向上取整數。

Python 程式碼


使用時間約為 29 ms,記憶體約為 5.2 MB,通過測試。
import sys, bisect

maxn = 44722  # 平方為 2000057284,超出題目上限 2000000000
nums = [i*i for i in range(maxn+1)]  # i 平方的數字,開頭放 0,用二分搜尋法時比較方便
for line in sys.stdin:
    n = int(line)  # 時間 n
    if n == 0: break
    m = bisect.bisect_left(nums, n)  # n 在 nums 之中的棋盤的第 m 列
    b = nums[m] - n  # n 是 nums 第 m 列往回數 b 格
    if m%2 == 1:  # 如果 m 是奇數,先向上,再向左
        if b < m: print(b+1, m)  # 倒過來向右數
        else: print(m, m+m-b-1)  # 倒過來向下數
    else:  # 如果 m 是偶數,先向右,再向下
        if b < m: print(m, b+1)  # 倒過來向上數
        else: print(m+m-b-1, m)  # 倒過來向左數

2026年2月7日 星期六

ZeroJudge 解題筆記:c045. 00490 - Rotating Sentences

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


ZeroJudge 題目連結:c045. 00490 - Rotating Sentences

解題想法


這題我在 Python 是用二維串列儲存字串內容,在 C++ 則是用 vector 加 string 儲存內容。先讀取所有的測資,接下來用二層 for 迴圈讀取測資中的字元,外層處理欄位,內容從最後一列讀取到第 0 列。輸出答案時,如果這一列不是整整空白才要輸出。

Python 程式碼


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

lines = sys.stdin.readlines()  # 一次讀取所有的資測,包含結尾的 \n
n = len(lines)  # 共有 n 列
ans = [[] for _ in range(100)]  # 答案,題目限制每列最多 100 個字元
for c in range(100):  # 欄位,題目限制每列最多 100 個字元
    for r in range(n-1, -1, -1):  # 依序讀取第 n-1 到 0 列
        m = len(lines[r]) - 1
        if c < m: ans[c].append(lines[r][c])  # 如果 r 沒有出界,lines[r][c] 加入 ans[c]
        else: ans[c].append(" ")  # 反之,空格加入 ans[c]
for a in ans:  # 依序讀取 ans 每一列的資料 a
    row = "".join(a)  # 將 a 組成字串 row
    if not row.isspace():  # 如果 row 不是整列空白
        sys.stdout.write(row + "\n")


2026年2月6日 星期五

ZeroJudge 解題筆記:c044. 10008 - What's Cryptanalysis

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


ZeroJudge 題目連結:c044. 10008 - What's Cryptanalysis

解題想法


這題可以用表格或字典計數,以下的寫法採用表格計數,用一個長度為 26 的陣列記錄每個字母出現的次數,不是英文字母的字元不需要計數。計數完畢之後,再將次數、字母組成 tuple 或 pair 存入陣列之中,先依照次數由大到小排序,如果次數相同再依照字母由小到大排序。

Python 程式碼


使用時間約為 21 ms,記憶體約為 3.3 MB,通過測試。
n = int(input())
cnt = [0]*26
for _ in range(n):
    s = input()
    for c in s:
        if c.isupper():
            cnt[ord(c) - ord('A')] += 1
        elif c.islower():
            cnt[ord(c) - ord('a')] += 1
ans = [(v, chr(k+ord('A'))) for k, v in enumerate(cnt) if v > 0]
ans.sort(key = lambda x : (-x[0], ord(x[1])))
for a in ans: print(a[1], a[0])


2026年2月5日 星期四

ZeroJudge 解題筆記:c039. 00100 - The 3n + 1 problem

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


ZeroJudge 題目連結:c039. 00100 - The 3n + 1 problem

解題想法


用一個自訂函式 cycle 處理數值 n 的循環次數,寫起來會比較方便。函式內部按照題目的要求,分別處理 n 為奇數、偶數兩種狀況,更新 n 的數值。

Python 程式碼


使用時間約為 2.3 s,記憶體約為 3.3 MB,通過測試。
import sys

def cycle(n):
    times = 1  # 如果 n 等於 1,至少有 1 次
    while n != 1:
        times += 1
        if n%2 == 1: n = 3*n + 1
        else: n //= 2
    return times

for line in sys.stdin:
    a, b = map(int, line.split())
    print(f"{a:d} {b:d} ", end="")
    if a > b: a, b = b, a
    imax = 0
    for i in range(a, b+1):
        imax = max(imax, cycle(i))
    print(f"{imax:d}")


2026年2月4日 星期三

ZeroJudge 解題筆記:c036. 00573 - The Snail

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


ZeroJudge 題目連結:c036. 00573 - The Snail

解題想法


用 $y$ 儲存目前的高度,預設值為 0。用一個無窮迴圈,不斷更新每天的高度,更新的順序為
  1. 白天往上爬高度 $u$,如果 $y > h$ 印出 success on day {days},中止迴圈。
  2. 晚上向下滑高度 $d$,如果 $y < 0$ 印出 failure on day {days},中止迴圈。
  3. 更新下一天向上爬的高度 $u$,如果 $u < 0$ 時歸零。


Python 程式碼


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

for line in sys.stdin:
    if line.rstrip() == "0 0 0 0": break
    h, u, d, f = map(float, line.split())
    days = 0
    dec = u*f*0.01
    y = 0.0
    while True:
        days += 1
        y += u
        if y > h:
            print(f"success on day {days:d}")
            break
        y -= d
        if y < 0:
            print(f"failure on day {days:d}")
            break
        u -= dec
        if u < 0: u = 0.0


2026年2月3日 星期二

ZeroJudge 解題筆記:c034. 00424 - Integer Inquiry

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


ZeroJudge 題目連結:c034. 00424 - Integer Inquiry

解題想法


Python 支援大數運算,直接加起來就好。C++ 則要用字串儲存數字,然後從最後一位往前加。

Python 程式碼


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

isum = 0
for line in sys.stdin:
    n = int(line)
    if n == 0: break
    isum += n
print(isum)


C++ 程式碼


使用時間約為 4 ms,記憶體約為 336 kB,通過測試。
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;

string big_sum(string s, string t) {  // 輸出字串 s, t,輸出相加後的字串 u
    string u = "";  // 儲存相加的結果
    int m = (int)s.size(), n = (int)t.size();  // s, t 的長度
    if (m > n) {  // s 較長,在 t 前面補 0
        string zeros (m-n, '0');
        t = zeros + t;
    } else if (m < n) {  // t 較長,在 s 前面補 0
        string zeros (n-m, '0');
        s = zeros + s;
    }
    int mn = max(m, n), carry = 0;  // 最大長度 mn,進位數值只會是 0 或 1
    for(int i=mn-1; i>=0; i--) {  // 由最後一位向前讀取
        char a = s[i], b = t[i];  // 字元 a, b
        int c = (a-'0') + (b-'0') + carry;  // a, b 相加後的結果 c
        u = char(c%10 + '0') + u;  // 更新 u
        carry = c/10;  // 更新 carry
    }
    if (carry == 1) u = '1' + u;  // 如果 carry 等於 1,在 u 前面補 1
    for(int i=0; i<(int)u.size(); i++) {  // 刪除 u 前面的 0
        if (u[i] != '0') {
            u = u.substr(i);
            break;
        }
    }
    return u;
}

int main() {
    ios::sync_with_stdio(0); cin.tie(0);
    string s, t;
    getline(cin, s);
    while(getline(cin, t)) s = big_sum(s, t);
    cout << s << "\n";
    return 0;
}


2026年2月2日 星期一

ZeroJudge 解題筆記:c033. 00406 - Prime Cuts

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


ZeroJudge 題目連結:c033. 00406 - Prime Cuts

解題想法


這個題目的 1 被視為質數。質數表最大值到 1009,因為測資的 n 最大值為 1000,如果只建到 1000,在質數表中用二分搜尋法找 n 可能會出界。

Python 程式碼


使用時間約為 19 ms,記憶體約為 3.3 MB,通過測試。
import sys, bisect

nums = [1]
state = [True]*1010
for i in range(2, 1010):
    if not state[i]: continue
    nums.append(i)
    for j in range(i+i, 1010, i):
        state[j] = False

first = True
for line in sys.stdin:
    n, c = map(int, line.split())
    if not first: print()
    first = False
    idx = bisect.bisect_left(nums, n)
    if nums[idx] == n: k = idx + 1
    else: k = idx
    if (k%2 == 0 and 2*c >= k) or \
       (k%2 == 1 and 2*c + 1 >= k):
        sub = nums[:k]
    elif k%2 == 0: sub = nums[k//2 - c : k//2 + c]
    else: sub = nums[(k+1)//2 - c : k//2 + c]
    print(f"{n:d} {c:d}: ", end="")
    print(*sub)


2026年2月1日 星期日

ZeroJudge 解題筆記:c032. 00382 - Perfection

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


ZeroJudge 題目連結:c032. 00382 - Perfection

解題想法


注意,實際上有多行測資。我是用試除法找 $n$ 的因數,將所有的因數儲存在集合之中。最後再比較所有的因數加總 $isum$ 與 $n$ 的大小,輸出對應的答案。

Python 程式碼


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

def test(n):
    if n == 1: return "DEFICIENT"
    factors = {1}
    for i in range(2, int(n**0.5)+1):
        if n%i == 0:
            factors.add(i)
            factors.add(n//i)
    isum = sum(factors)
    if isum == n:
        return "PERFECT"
    elif isum < n:
        return "DEFICIENT"
    else:
        return "ABUNDANT"

print("PERFECTION OUTPUT")
for line in sys.stdin:
    for n in map(int, line.split()):
        if n == 0: continue
        print(f"{n:5d}  {test(n):s}")
print("END OF OUTPUT")


2026年1月31日 星期六

ZeroJudge 解題筆記:c031. 00264 - Count on Cantor

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


ZeroJudge 題目連結:c031. 00264 - Count on Cantor

解題想法


先觀察數列的特性,第 $i$ 列共有 $i$ 項,數值為 $1/i, 2/(i-1), \dots, (i-1)/2, i/1$,第 $1$ 到 $i$ 列總數 $$ total = 1 + 2 + \dots + i = \frac{i(i+1)}{2} $$ 因此,我先建一個陣列 $nums$ 儲存第 0 列到第 $i$ 列的項目數量,直到數量大於等於 $10^7$ 為止。讀取測資 $n$ 之後,再用二分搜尋法從 $nums$ 之中找 $n$ 的索引值 $m$。若第 $n$ 項為 $p/q$,由於題目的數列順序為 S 形,如果 m 是奇數,代表取數值方向往右上,則 $p = nums[m] - n +1, q = m - p +1$;反之,方向往左下。則 $q = nums[m] - n +1, p = m - p +1$。

Python 程式碼


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

nums = [0]
i, tot = 0, 0
while tot < 1E7:
    i += 1
    tot += i
    nums.append(tot)

for line in sys.stdin:
    n = int(line)
    m = bisect.bisect_left(nums, n)
    if m%2 == 1:
        p = nums[m] - n + 1
        q = m - p + 1
    else:
        q = nums[m] - n + 1
        p = m - q + 1
    print(f"TERM {n:d} IS {p:d}/{q:d}")


2026年1月30日 星期五

ZeroJudge 解題筆記:c024. 10079 - Pizza Cutting

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


ZeroJudge 題目連結:c024. 10079 - Pizza Cutting

解題想法


這題困難之處在於數學證明。假設切 $n$ 刀的最大分割數量為 $m$ 塊,先列出前幾項的數量 1. $n = 0, m = 1$ 2. $n = 1, m = 1 + 1 = 2$ 3. $n = 2, m = 2 + 2 = 4$ 4. $n = 3, m = 3 + 4 = 7$ 5. $n = 4, m = 4 + 7 = 11$ 如果要使分割數量最多,最後一刀要與前面每一刀都交會。若 $dp[n]$ 代表切 $n$ 刀分割的最大數量,由以上的式子可以看出 $$ \begin{align*} dp[n] &= dp[0] + dp[n-1] + n\\ &= 1 + 1 + 2 + \dots + (n-1) + n\\ &= 1 + \frac{n(n+1)}{2} \\ &= \frac{n^2 + n + 2}{2} \end{align*} $$

Python 程式碼


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

for line in sys.stdin:
    n = int(line)
    if n < 0: break
    print((n*n + n + 2) // 2)


2026年1月29日 星期四

ZeroJudge 解題筆記:c012. 10062 - Tell me the frequencies!

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


ZeroJudge 題目連結:c012. 10062 - Tell me the frequencies!

解題想法


這題如果用 Python 解題,可以用預設的 map 或是 collections 函式庫中的 Counter 計數,用 Counter 比較方便,只要寫 Counter(字串) 就可以算出字串每個字元的個數。測資會有空白,如果用 C++ 要用 getline 讀取資料,但是不能計算到結尾的 \n 或 \r。

Python 程式碼


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

first = True
for line in sys.stdin:
    if not first: print()
    first = False
    cnt = Counter(line.strip())
    ans = [(v, ord(k)) for k, v in cnt.items()]
    ans.sort(key = lambda x : (x[0], -x[1]))
    for a in ans: print(a[1], a[0])

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

result = []
lines = sys.stdin.readlines()
for line in lines:
    if result: result.append("\n")
    cnt = Counter(line.strip())
    ans = [(v, ord(k)) for k, v in cnt.items()]
    ans.sort(key = lambda x : (x[0], -x[1]))
    for a in ans: result.append(f"{a[1]:d} {a[0]:d}\n")
sys.stdout.write("".join(result))


2026年1月28日 星期三

ZeroJudge 解題筆記:c014. 10035 - Primary Arithmetic

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


ZeroJudge 題目連結:c014. 10035 - Primary Arithmetic

解題想法


這題只要依照直式加法的作法,從最後一位開始相加,如果最後一個分別為 a, b,目前的進位值為 carry,如果 $a + b + carry \geq 10$ 需要進位,進位次數 $cnt$ 加 1,同時 $carry$ 重設為 1;反之不需要進位,$carry$ 重設為 0。因為測資的數字在 10 位數以內,可以直接用整數處理就好;但如果數字很大,需要用字串格式才能處理。

Python 程式碼


整數格式,使用時間約為 22 ms,記憶體約為 2.9 MB,通過測試。
import sys

for line in sys.stdin:
    n, m = map(int, line.split())
    if n == 0 and m == 0: break
    cnt, carry = 0, 0
    while n > 0 or m > 0:
        a, b = n%10, m%10
        if a + b + carry >= 10:
            cnt += 1
            carry = 1
        else:
            carry = 0
        n //= 10
        m //= 10
    if cnt == 0:
        print("No carry operation.")
    elif cnt == 1:
        print("1 carry operation.")
    else:
        print(f"{cnt:d} carry operations.")

字串格式,使用時間約為 25 ms,記憶體約為 2.9 MB,通過測試。
import sys

for line in sys.stdin:
    s, t = line.split()
    if s == "0" and t == "0": break
    n, m = len(s), len(t)  # 長度
    # 前面補 0 讓 s, t 長度相等
    if n < m:
        s = s.zfill(m)
    else:
        t = t.zfill(n)
    # 由後往前計算加法及進位
    cnt, carry = 0, 0  # 進位次數,目前進位的值
    for a, b in zip(s[::-1], t[::-1]):
        if int(a) + int(b) + carry >= 10:
            cnt += 1
            carry = 1
        else:
            carry = 0
    # 輸出對應的答案
    if cnt == 0:
        print("No carry operation.")
    elif cnt == 1:
        print("1 carry operation.")
    else:
        print(f"{cnt:d} carry operations.")