置頂

我的 VPython 教學文件 (HackMD 版本)

VPython 教學文件目錄 安裝及測試 基本語法 等速度直線運動 自由落下 終端速度 水平抛射 使用For迴圈計算水平抛射資料 斜向抛射 圓周運動 簡諧運動 單擺 木塊彈簧系統分離 重力及簡諧 行星運動 相疊木塊 雙重簡諧運動 一維彈性碰撞 ...

熱門文章

2025年12月10日 星期三

ZeroJudge 解題筆記:n688. pC. 卡牌遊戲

作者:王一哲
日期:2025年12月10日


ZeroJudge 題目連結:n688. pC. 卡牌遊戲

解題想法


先將卡牌分別正值、負值兩堆,為了將卡牌總和最大化,策略是將負值牌堆中最小的牌,也就是負值、量值最大的牌取出、轉為正值、存入正值牌堆,如果負值牌堆沒有資料了,就反過來將正值牌堆中最小的牌取出、轉為負值、存入負值牌堆。為了達成這個目標,用最小優先佇列儲存牌堆是最方便的。

Python 程式碼


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

n, k = map(int, input().split())
positive, negative = [], []
for x in map(int, input().split()):
    if x >= 0: heapq.heappush(positive, x)
    else: heapq.heappush(negative, x)
for _ in range(k):
    if negative:
        heapq.heappush(positive, -heapq.heappop(negative))
    elif positive:
        heapq.heappush(negative, -heapq.heappop(positive))
print(sum(positive) + sum(negative))


2025年12月9日 星期二

ZeroJudge 解題筆記:n083. 數字序號轉換

作者:王一哲
日期:2025年12月9日


ZeroJudge 題目連結:n083. 數字序號轉換

解題想法


假設計算結果儲存在變數 m,m 的初始值為 0。將讀到的字串從索引值 0 開始,每次取長度為 3 的子字串,並用一個布林值記錄這次是否需要將子字串反序。m 加上子字串轉成 int 之後的值,再對 997 取餘數。

Python 程式碼


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

MOD = 997
result = []
lines = sys.stdin.readlines()
for line in lines:
    line = line.strip()
    n = len(line)
    m = 0
    rev = False
    for i in range(0, n, 3):
        s = line[i:i+3]
        if rev: s = s[::-1]
        rev = not rev
        m = (m + int(s))%MOD
    result.append(str(m) + "\n")
sys.stdout.write("".join(result))


2025年12月8日 星期一

ZeroJudge 解題筆記:b181. 2. 網路設計

作者:王一哲
日期:2025年12月8日


ZeroJudge 題目連結:b181. 2. 網路設計

解題想法


這題用自訂的併查集類別建立物件,依序從成本最低的邊開始測試,如果這條邊加上去之後,能夠使兩個節點所在的區塊連通,就要選用這條邊,記錄節點編號並更新總成本。 題目沒有說清楚的地方有三點:
  1. 這題是多筆測資
  2. n 只是節點編號的最大值,不見得有 n 個節點。
  3. 節點的排序方式,讀取測資儲存邊的成本、節點時要按照測資的順序。輸出節點時要按照節點的編號由小到大排序,轉換成整數再比大小,不用管開頭的 W,兩個節點一組,先比較第一個節點,再比較第二個節點。輸出時如果按照字典序排序會出錯
    WA (line:3)
    您的答案為: (W10 W2) (W2 W3) (W3 W4) (W3 W5) (W3 W6)
    正確答案為: (W2 W3) (W3 W4) (W3 W5) (W3 W6) (W10 W2)
    
    如果按照第一個節點的編號數字排序也會出錯
    WA (line:3)
    您的答案為: (W2 W3) (W3 W5) (W3 W4) (W3 W6) (W10 W2)
    正確答案為: (W2 W3) (W3 W4) (W3 W5) (W3 W6) (W10 W2)
    


Python 程式碼


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

class DisjointSetUnion:
    def __init__(self, nodes):
        self.parent = {node: node for node in nodes}
        self.rank = {node: 0 for node in nodes}

    def rfind(self, x):
        if self.parent[x] == x:
            return x
        self.parent[x] = self.rfind(self.parent[x])
        return self.parent[x]

    def union(self, u, v):
        root_u, root_v = self.rfind(u), self.rfind(v)
        if root_u != root_v:
            if self.rank[root_u] > self.rank[root_v]:
                self.parent[root_v] = root_u
            elif self.rank[root_v] > self.rank[root_u]:
                self.parent[root_u] = root_v
            else:
                self.parent[root_v] = root_u
                self.rank[root_u] += 1
            return True
        return False

def mycmp(a, b):
    """ 為了輸出節點定義的比較式 """
    if int(a[0][1:]) == int(b[0][1:]):  # 如果第一個節點的編號相同
        if int(a[1][1:]) < int(b[1][1:]): return -1  # 第二個節點編號小的放前面
        elif int(a[1][1:]) > int(b[1][1:]): return 1
        else: return 0
    if int(a[0][1:]) < int(b[0][1:]): return -1  # 第一個節點編號小的放前面
    else: return 1

""" 處理多筆測資 """
result = []
lines = sys.stdin.readlines()
idx = 0
while idx < len(lines):
    """ 讀取測資 """
    n, m = map(int, lines[idx].split())  # 題目敘率不清,n 是可能的節點編號最大值,m 條邊
    idx += 1
    nodes = set()  # 所有節點,不見得有 n 個節點,要從邊的資料找節點
    edges = []  # 所有的邊
    for _ in range(m):  # 讀取 m 條邊
        line = lines[idx].split()
        idx += 1
        u, v = line[0], line[1]  # 節點 u, v 相連
        w = int(line[2])  # 邊的成本
        edges.append((w, u, v))  # (成本, 節點1, 節點2),節點要按照測資的順序
        nodes.add(u)
        nodes.add(v)

    """ 用併查集找最低合併成本 """
    dsu = DisjointSetUnion(nodes)  # 建立併查集物件
    edges.sort()  # 依照成本由小到大排序
    cost = 0  # 總成本
    choose = []  # 選取的節點
    for w, u, v in edges:  # 依序讀取邊
        if dsu.union(u, v):  # 如果 u, v 可以合併
            cost += w  # 更新成本
            choose.append((u, v))  # 更新選取的節點
    """ 輸出答案 """
    choose.sort(key = cmp_to_key(mycmp))  # 輸出節點時,要按照節點編號大小排序,比數字
    res = []
    for u, v in choose:
        res.append(f"({u} {v})")
    result.append(" ".join(res) + "\n" + f"{cost:d}\n")
""" 一次輸出所有答案 """
sys.stdout.write("".join(result))


2025年12月7日 星期日

ZeroJudge 解題筆記:m702. 傑出校友票選活動

作者:王一哲
日期:2025年12月7日


ZeroJudge 題目連結:m702. 傑出校友票選活動

解題想法


這題用字典計數比較方便。如果用 Python 解題,可以用預設的 dict 計數,再將 dict 的 (value, key) 組成 tuple 存入另一個 list,將 list 依照 value 由大到小排序;也可以先將所有的選票儲存到一個串列中,用 collections.Counter 計數,最後用 Counter 的功能 most_common 取出前 m 個最大的值。

Python 程式碼


使用預設的 dict,使用時間約為 0.2 s,記憶體約為 27.1 MB,通過測試。
n, m = map(int, input().split())
cnt = dict()
for _ in range(n):
    name = input()
    if name not in cnt: cnt[name] = 1
    else: cnt[name] += 1
    
ballot = sorted([(val, name) for name, val in cnt.items()], reverse=True)
print(" ".join([name for _, name in ballot[:m]]))

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

n, m = map(int, input().split())
ballot = [input() for _ in range(n)]
cnt = Counter(ballot)
print(*[name for name, _ in cnt.most_common(m)])


2025年12月6日 星期六

ZeroJudge 解題筆記:m685. 三角形計數器

作者:王一哲
日期:2025年12月6日


ZeroJudge 題目連結:m685. 三角形計數器

解題想法


這題要找共有幾組邊長比例不同的三角形。我是將讀到的一個三角形邊長由小到大排序,再用三個邊長的最大公因數約分,最後將約分後的邊長組成 tuple 或 vector 存進 set。讀取完所有測資之後,set 的大小就是答案。

Python 程式碼


使用時間約為 0.6 s,記憶體約為 23.9 MB,通過測試。
import math

n = int(input())
triangle = set()
for _ in range(n):
    a, b, c = sorted(list(map(int, input().split())))
    g = math.gcd(a, math.gcd(b, c))
    a, b, c = a//g, b//g, c//g
    triangle.add((a, b, c))
print(len(triangle))


2025年12月5日 星期五

ZeroJudge 解題筆記:m283. 螞蟻的擴散

作者:王一哲
日期:2025年12月5日


ZeroJudge 題目連結:m283. 螞蟻的擴散

解題想法


這題可以用一個二維陣列 $dp$ 記錄螞蟻從 $(a, b)$ 開始移動到每一格的機率,走到 $x = 0$ 或 $y = 0$ 時停止。走到格子 $(r, c)$ 的機率為 $$ dp[r][c] = (dp[r+1][c] + dp[r][c+1] + dp[r+1][c+1]) \times \frac{1}{3} $$ 用二層 for 迴圈計算往 $(0, 0)$ 方向走到每一格的機率。最後的答案等於 $1 - dp[0][0]$。 如果用 Python 解題,可以引入 fractions.Fraction 計算分數,也可以先計算分子最後再約分。

Python 程式碼


用 fractions.Fraction 計算分數,使用時間約為 53 ms,記憶體約為 4.1 MB,通過測試。
import sys
from fractions import Fraction

for line in sys.stdin:
    y, x = map(int, line.split())
    dp = [[0]*(x+1) for _ in range(y+1)]
    dp[y][x] = Fraction(1, 1)
    for r in range(y-1, -1, -1):
        for c in range(x-1, -1, -1):
            dp[r][c] = (dp[r+1][c] + dp[r][c+1] + dp[r+1][c+1])*Fraction(1, 3)
    print(1 - dp[0][0])

2025年12月4日 星期四

ZeroJudge 解題筆記:k862. 輩份比較

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


ZeroJudge 題目連結:k862. 輩份比較

解題想法


我用字典儲存每個節點的父節點與子節點,存完圖之後再找出沒有父節點的節點名稱,這個節點就是根節點。接下來由兩個要比較輩份的節點出發,由下往上走到根節點,找出與根節點之間的距離。最後將兩個距離相減就是輩份差。

Python 程式碼


使用時間約為 18 ms,記憶體約為 3.4 MB,通過測試。
n = int(input())  # n 組關係
parent = dict()  # 父節點
child = dict()  # 子節點
for _ in range(n):  # 讀取 n 行資料
    a, b = input().split()  # a 是 b 的子節點
    if b not in child: child[b] = [a]  # 更新子節點
    else: child[b].append(a)
    if a not in parent: parent[a] = b  # 更新父節點
root = ""  # 根節點
for b in child.keys():  # 依序取出 child 的 key 值 b
    if b not in parent:  # 如果 b 沒有父節點
        root = b  # b 是根節點
        break
p, q = input().split()  # 如果 p 是晚輩輸出負值,如果 p 是長輩輸出正值,平輩輸出 0
dp, dq = 0, 0  # 從根節點往下算的距離
curr = p  # 目前的節點
while curr != root:  # 從 p 往上走到根節點
    dp += 1
    curr = parent[curr]
curr = q  # 目前的節點
while curr != root:  # 從 q 往上走到根節點
    dq += 1
    curr = parent[curr]
print(dq - dp)  # 輸出答案

2025年12月3日 星期三

ZeroJudge 解題筆記:k645. 數學得力工具

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


ZeroJudge 題目連結:k645. 數學得力工具

解題想法


這題要先用 + 分割字串,取出字串中的數字,將數字由小到大排序,再將數字用 + 連接後輸出。如果用 Python 解題,可以用 split 分割字串,用 sorted 排序數字,用 join 連接字串,一行解。用 C++ 解題則會比較麻煩一點。

Python 程式碼


一行解,使用時間約為 40 ms,記憶體約為 3.3 MB,通過測試。
print("+".join(sorted(list(input().split("+")))))


C++ 程式碼


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

int main() {
    ios::sync_with_stdio(0); cin.tie(0);
    string s, num = ""; getline(cin, s);
    vector<string> eq;
    for(char c : s) {
        if (c == '+') {
            eq.push_back(num);
            num = "";
        } else {
            num += c;
        }
    }
    eq.push_back(num);
    sort(eq.begin(), eq.end());
    for(int i=0; i<(int)eq.size(); i++) {
        cout << eq[i] << "+\n"[i == (int)eq.size()-1];
    }
    return 0;
}


2025年12月2日 星期二

ZeroJudge 解題筆記:k622. [棕]0-1背包問題

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


ZeroJudge 題目連結:k622. [棕]0-1背包問題

解題想法


標準的背包問題。用一個陣列 dp 代表各種總重量對應的最大價值總和,依序讀取每樣物品的價值 p 及重量 w,從 dp 最後一項往回檢查到 dp[w] 為止,更新每一項的最大價值總和。

題目敘述有誤,物品重量上限不是 $10^4$,是 $10^7$。由於測資中物品重量大值為 $10^7$,用 C++ 解題時,如果將 array 開在 main 之中,倒數第二筆測資會遇到記憶體區段錯誤;如果將 array 開在 main 外面,在記憶體限制為 64 MB 的條件下,理論上最大長度為 $1.6 \times 10^7$ ,但我送程解答時會遇到記憶體區段錯誤。改用 vector 才能過關。至於 Python 則是怎麼樣都過不了。

這題的物品價值上限為 $10^8$,數量上限為 $10^9$,理論上價值總和應該會超過 int 的上限。如果用 long long 儲存 dp 的資料,會超過記憶體上限。改用 int 儲存 dp 的資料才能過關。



Python 程式碼


倒數第2筆測資超出記憶體上限,最後一筆測資超時。
n, k = map(int, input().split())
weight = list(map(int, input().split()))
price = list(map(int, input().split()))
dp = [0]*(k+1)
for w, p in zip(weight, price):
    for i in range(k, w-1, -1):
        dp[i] = max(dp[i], dp[i-w] + p)
print(dp[-1])


2025年12月1日 星期一

ZeroJudge 解題筆記:k621. [紅]括號匹配問題

作者:王一哲
日期:2025年12月1日


ZeroJudge 題目連結:k621. [紅]括號匹配問題

解題想法


自訂函式 check,輸入一行字串 s,檢查 s 之中的括號是否能配對。用堆疊 st 儲存讀到的左括號,如果讀到右括號,檢查 st 最後一項是否能與這個右括號配對,如果可以配對則移除 st 最後一項;如果不能配對則印出 Wrong,跳出函式。如果函式可以執行到最後一行,印出 Right。

Python 程式碼


使用時間約為 18 ms,記憶體約為 3.3 MB,通過測試。
def check(s):
    st = []
    left = ("{", "[", "(")
    right = {"}": 0, "]": 1, ")":2}
    for c in s:
        if c in "{[(":
            st.append(c)
        else:
            if st and st[-1] == left[right[c]]:
                st.pop()
            else:
                print("Wrong")
                return
    print("Right")

check(input())