Skip to content

A1: 競技程式設計師的工具箱:Python 環境與高速 I/O

單元目標

  • 理解標準輸入/輸出的概念
  • 掌握 Python 高速 I/O 的最佳實踐
  • 能夠處理各種格式的輸入資料
  • 建立性能優先的編程思維

為什麼 I/O 是第一課?

在競技程式設計的領域,程式的總執行時間由「I/O 時間」與「運算時間」共同構成。一個常見的誤區是,初學者往往只關注運算邏輯的優化,而忽略了 I/O 可能成為效能瓶頸

在 APCS 這類輸入資料量可能龐大的檢測中,緩慢的 I/O 會導致即使擁有最優演算法的程式,也因「執行超時」(Time Limit Exceeded, TLE)而無法通過。

效能陷阱

使用 input() 讀取大量資料時,可能會因為 I/O 緩慢而導致 TLE,即使你的演算法是正確且高效的!

這個現實要求我們必須顛覆傳統的教學順序。高速 I/O 並非「進階主題」,而是學習者從零開始就必須掌握的第一課。將高效能的習慣融入基礎,是建造鋼鐵地基的第一步。

概念基礎

標準輸入與標準輸出

**標準輸入(stdin)標準輸出(stdout)**是程式與評測系統(Judge)溝通的唯一橋樑:

  • 所有問題的輸入資料都從 stdin 讀取
  • 所有答案都必須輸出至 stdout

在 Python 中:

  • input()sys.stdin 用於讀取標準輸入
  • print()sys.stdout.write() 用於寫入標準輸出

I/O 效能比較

讓我們先來看一個效能測試:

python
# 測試:讀取 100,000 行整數

# 方法一:使用 input()
import time
start = time.time()
for _ in range(100000):
    n = int(input())
print(f"input() 耗時: {time.time() - start:.3f} 秒")

# 方法二:使用 sys.stdin.readline()
import sys
start = time.time()
for _ in range(100000):
    n = int(sys.stdin.readline())
print(f"sys.stdin.readline() 耗時: {time.time() - start:.3f} 秒")

結果sys.stdin.readline() 通常快 2-5 倍

Python 實踐

慢速路徑(應避免)

input() 函式是 Python 內建的標準輸入方式:

python
# 讀取單一整數
n = int(input())

# 讀取一行字串
s = input()

# 讀取一行由空白分隔的多個整數
arr = list(map(int, input().split()))

問題input() 簡單直觀,適合小型互動式腳本。然而,其內部處理機制相對複雜,對於大量資料的讀取效能不彰,是競技程式中的效能陷阱

專業路徑(標準作法)⭐

為了達到最高 I/O 效率,應使用 sys 模組。sys.stdin.readline() 直接從輸入緩衝區讀取一行資料,速度遠快於 input()

基本輸入模板

python
import sys

# 讀取單一整數
n = int(sys.stdin.readline())

# 讀取單一浮點數
x = float(sys.stdin.readline())

# 讀取一行字串(會保留換行符號)
s = sys.stdin.readline()

# 讀取一行字串並移除前後空白與換行
s = sys.stdin.readline().strip()

# 讀取一行由空白分隔的多個整數
arr = list(map(int, sys.stdin.readline().split()))

# 讀取一行由空白分隔的多個字串
words = sys.stdin.readline().split()

詳細解說

python
import sys

# 讀取單一整數
# sys.stdin.readline() 會讀取一整行,包含換行符號 '\n'
# 因此需要 int() 轉換,int() 會自動忽略前後空白與換行
n = int(sys.stdin.readline())

# 讀取一行由空白分隔的多個整數
# .strip() 用於移除行末的換行符號
# .split() 將字串按空白分割成字串列表
# map(int, ...) 將每個字串元素轉換為整數
# list(...) 將 map 物件轉換為列表,方便後續操作
arr = list(map(int, sys.stdin.readline().strip().split()))

常見輸入格式處理

格式 1:第一行是 N,接下來 N 行資料

python
import sys

n = int(sys.stdin.readline())
for _ in range(n):
    # 讀取每一行並處理
    line = sys.stdin.readline().strip()
    # 或讀取整數
    num = int(sys.stdin.readline())

格式 2:讀取多組測資直到 EOF

python
import sys

for line in sys.stdin:
    # 處理每一行
    data = line.strip()
    # 或轉換為整數列表
    nums = list(map(int, line.split()))

格式 3:讀取矩陣(二維陣列)

python
import sys

# 讀取 n x m 的矩陣
n, m = map(int, sys.stdin.readline().split())
matrix = []
for _ in range(n):
    row = list(map(int, sys.stdin.readline().split()))
    matrix.append(row)

高效輸出

對於輸出,sys.stdout.write()print() 的高速對應:

python
import sys

# 使用 print()(通常已經夠快)
print(result)

# 使用 sys.stdout.write()(最快)
# 注意:需要手動轉換為字串並添加換行符號
sys.stdout.write(str(result) + '\n')

# 批量輸出(效能更好)
results = 
# ... 計算結果 ...
print('\n'.join(map(str, results)))

實務建議

在大多數 APCS 題目中,print() 的速度已經足夠。只有在輸出量極大時,才需要考慮使用 sys.stdout.write()

重點是輸入:一定要使用 sys.stdin.readline()

完整範例程式

範例 1:讀取 N 個整數並輸出總和

python
import sys

# 讀取 N
n = int(sys.stdin.readline())

# 讀取 N 個整數
numbers = list(map(int, sys.stdin.readline().split()))

# 計算總和
total = sum(numbers)

# 輸出結果
print(total)

輸入範例

5
1 2 3 4 5

輸出

15

範例 2:讀取多組測資

python
import sys

# 讀取所有行直到 EOF
for line in sys.stdin:
    # 將這一行轉換為整數列表
    numbers = list(map(int, line.split()))
    
    # 計算總和
    total = sum(numbers)
    
    # 輸出結果
    print(total)

輸入範例

1 2 3
4 5
6 7 8 9

輸出

6
9
30

APCS 策略與應用

此技能直接對應 APCS 評量架構中的「輸入與輸出」及「算術運算」項目。

實際題目應用

練習考古題如:

  • i399 (數字遊戲)
  • o076 (特技表演)

這些題目的核心不在於複雜的邏輯,而在於:

  1. 熟練運用 I/O 模板
  2. 正確地接收輸入
  3. 進行基本運算
  4. 格式化輸出

📝 Quiz:測試你的理解

問題 1

在處理 APCS 大量輸入時,下列哪種 Python 寫法效率最高?

A. data = input()
B. import sys; data = sys.stdin.readline()
C. 兩種寫法效率沒有差別

點擊查看解答

答案:B

解析
input() 函式為了兼容互動式情境,內部處理較為複雜,速度較慢。而 sys.stdin.readline() 直接從標準輸入緩衝區讀取,速度快上許多,是競技程式設計的標準作法,能有效避免因 I/O 緩慢導致的「執行超時 (TLE)」。


問題 2

以下程式碼的功能是什麼?

python
arr = list(map(int, sys.stdin.readline().split()))

A. 讀取一行字串
B. 讀取一個整數
C. 讀取一行由空白分隔的多個整數,存入列表
D. 讀取多行整數

點擊查看解答

答案:C

解析

  • sys.stdin.readline() 讀取一行
  • .split() 將字串按空白分割成列表
  • map(int, ...) 將每個字串轉換為整數
  • list(...) 將 map 物件轉換為列表

這是處理「一行多個整數」的標準寫法,非常常用!


問題 3

為什麼需要使用 .strip().split()

A. 為了讓程式碼看起來更專業
B. 為了移除字串中的換行符號和空白
C. 為了加快程式執行速度
D. 這不是必需的

點擊查看解答

答案:B

解析
sys.stdin.readline() 讀取的字串會包含行末的換行符號 \n。使用 .strip() 可以移除前後的空白字元(包括換行、空格、Tab 等),確保資料處理的正確性。

.split() 則是用於將字串分割成多個部分,同時也會自動移除空白。

🔗 推薦習題

基礎練習

練習重點

  1. 正確讀取不同格式的輸入
  2. 使用 sys.stdin.readline() 而非 input()
  3. 處理基本的算術運算
  4. 格式化輸出

學習建議

完成這些題目後,確保你能夠:

  • [ ] 不看範例,獨立寫出 I/O 模板
  • [ ] 在 1 分鐘內寫出讀取「N 個整數」的程式碼
  • [ ] 理解何時使用 .strip().split()

📚 參考資料


下一步

掌握了高速 I/O 後,我們將進入邏輯控制的核心:

前往 A2:條件判斷與迴圈 →


Released under the MIT License.