Bài 1.13. Thực hành Python: xử lý input theo format thi đấu

Bài 1.13. Thực hành Python: xử lý input theo format thi đấu
Chào mừng các bạn đến với bài thực hành quan trọng này! Trong thế giới lập trình thi đấu (Competitive Programming - CP), việc đọc và xử lý dữ liệu đầu vào (input) một cách nhanh chóng và chính xác là một kỹ năng tối quan trọng. Các bài toán thường có giới hạn thời gian chặt chẽ, và việc xử lý input không hiệu quả có thể dẫn đến "Time Limit Exceeded" (TLE) ngay cả khi thuật toán của bạn hoàn toàn đúng đắn.
Các kỳ thi thường cung cấp input theo một định dạng chuẩn rất cụ thể thông qua standard input (stdin). Nhiệm vụ của chúng ta là viết code Python có thể đọc đúng định dạng này và chuyển đổi thành các biến, cấu trúc dữ liệu phù hợp để giải quyết bài toán.
Bài viết này sẽ hướng dẫn bạn các kỹ thuật phổ biến và hiệu quả nhất để xử lý các dạng input thường gặp trong thi đấu bằng Python. Chúng ta sẽ đi từ những trường hợp đơn giản đến phức tạp hơn, kèm theo ví dụ minh họa và giải thích chi tiết.
1. Đọc Input Cơ Bản với input()
Hàm input()
là cách cơ bản nhất để đọc một dòng từ standard input trong Python. Nó đọc toàn bộ dòng cho đến khi gặp ký tự xuống dòng (\n
) và trả về dưới dạng một chuỗi (string).
a. Đọc một giá trị duy nhất trên một dòng
Đây là trường hợp đơn giản nhất. Ví dụ, input chỉ có một số nguyên N
hoặc một chuỗi S
.
Ví dụ Input:
5
Hoặc:
Hello Competitive Programming!
Code xử lý:
# Đọc một số nguyên
n_str = input() # Đọc dòng "5" vào biến n_str (kiểu string)
n = int(n_str) # Chuyển đổi chuỗi "5" thành số nguyên 5
print(f"Đã đọc số nguyên: {n}")
# Đọc một chuỗi
s = input() # Đọc dòng "Hello Competitive Programming!" vào biến s
print(f"Đã đọc chuỗi: {s}")
Giải thích:
input()
luôn trả về một chuỗi. Nếu bạn cần một số (nguyên hoặc thực), bạn phải ép kiểu tường minh bằngint()
hoặcfloat()
.
b. Đọc nhiều giá trị trên cùng một dòng (cách nhau bởi khoảng trắng)
Đây là một định dạng cực kỳ phổ biến. Ví dụ, đọc hai số nguyên N
và M
, hoặc một danh sách các số trên cùng một dòng.
Ví dụ Input:
10 20
Hoặc:
1 5 2 8 3
Code xử lý:
# Đọc hai số nguyên N và M
line = input() # Đọc dòng "10 20"
parts = line.split() # Tách chuỗi thành list các chuỗi con: ['10', '20']
n_str = parts[0]
m_str = parts[1]
n = int(n_str) # Chuyển "10" thành 10
m = int(m_str) # Chuyển "20" thành 20
print(f"Đã đọc N={n}, M={m}")
# Cách ngắn gọn hơn sử dụng map()
n, m = map(int, input().split())
print(f"Đã đọc (cách 2) N={n}, M={m}")
# Đọc một danh sách các số nguyên
numbers_str = input().split() # Đọc "1 5 2 8 3" và tách thành ['1', '5', '2', '8', '3']
numbers = list(map(int, numbers_str)) # Áp dụng int() cho từng phần tử và chuyển thành list
print(f"Đã đọc danh sách số: {numbers}")
# Cách siêu ngắn gọn
numbers = list(map(int, input().split()))
print(f"Đã đọc danh sách số (cách 2): {numbers}")
Giải thích:
input().split()
: Đọc một dòng và dùng phương thức.split()
để tách chuỗi đó thành một list các chuỗi con. Mặc định,.split()
tách dựa trên khoảng trắng (space).map(function, iterable)
: Áp dụngfunction
(ở đây làint
) lên từng phần tử củaiterable
(ở đây là list các chuỗi số).map()
trả về một đối tượng map, nên chúng ta thường dùnglist()
để chuyển nó thành list hoặc dùng unpacking (nhưn, m = ...
) nếu biết trước số lượng phần tử.
2. Đọc Nhiều Dòng Input
Các bài toán thường yêu cầu đọc nhiều dòng dữ liệu.
a. Đọc N dòng, mỗi dòng một giá trị
Thường gặp khi input cho biết số lượng N
ở dòng đầu tiên, sau đó là N
dòng dữ liệu.
Ví dụ Input:
3
Apple
Banana
Cherry
Code xử lý:
n = int(input()) # Đọc số lượng dòng N = 3
items = [] # Khởi tạo list rỗng để chứa dữ liệu
for _ in range(n): # Lặp N lần
item = input() # Đọc một dòng trong mỗi lần lặp
items.append(item) # Thêm vào list
print(f"Đã đọc {n} mục: {items}")
# Cách dùng list comprehension (ngắn gọn và Pythonic hơn)
n = int(input())
items_comprehension = [input() for _ in range(n)]
print(f"Đã đọc (comprehension) {n} mục: {items_comprehension}")
Giải thích:
- Đầu tiên, đọc
N
. - Sau đó, sử dụng vòng lặp
for _ in range(n):
để lặp đúngN
lần. Dấu gạch dưới_
là một quy ước dùng khi chúng ta không cần giá trị của biến lặp (chỉ cần lặp đủ số lần). - Trong mỗi lần lặp,
input()
đọc một dòng vàappend()
thêm nó vào danh sáchitems
. - List comprehension
[input() for _ in range(n)]
là cách viết tương đương, thường ngắn gọn và đôi khi hiệu quả hơn vòng lặpfor
truyền thống.
b. Đọc N dòng, mỗi dòng nhiều giá trị
Tương tự như trên, nhưng mỗi dòng trong N
dòng lại chứa nhiều giá trị cách nhau bởi khoảng trắng. Thường dùng để đọc ma trận hoặc danh sách các cặp/tuple.
Ví dụ Input (đọc ma trận 2x3):
2 3
1 2 3
4 5 6
Code xử lý:
# Đọc số hàng R và số cột C
R, C = map(int, input().split())
matrix = [] # Khởi tạo list rỗng để chứa ma trận (list các list)
for _ in range(R): # Lặp R lần (cho mỗi hàng)
row_str = input().split() # Đọc dòng "1 2 3" -> ['1', '2', '3']
row_int = list(map(int, row_str)) # Chuyển thành list số nguyên [1, 2, 3]
matrix.append(row_int) # Thêm hàng vào ma trận
print(f"Đã đọc ma trận {R}x{C}:")
for row in matrix:
print(row)
# Cách dùng list comprehension
R, C = map(int, input().split()) # Đọc lại kích thước nếu chạy riêng đoạn này
matrix_comprehension = [list(map(int, input().split())) for _ in range(R)]
print(f"Đã đọc ma trận (comprehension) {R}x{C}:")
for row in matrix_comprehension:
print(row)
Giải thích:
- Đọc kích thước
R
,C
trước. - Lặp
R
lần, mỗi lần đọc một dòng,.split()
nó, dùngmap(int, ...)
để chuyển thành các số nguyên, vàlist(...)
để tạo thành một list đại diện cho hàng đó. - Cuối cùng,
append
list hàng này vào listmatrix
. - List comprehension
[list(map(int, input().split())) for _ in range(R)]
thực hiện chính xác các bước tương tự một cách cô đọng.
3. Tối ưu tốc độ đọc Input với sys.stdin
Khi dữ liệu input rất lớn (hàng trăm nghìn hoặc hàng triệu dòng), hàm input()
có thể trở nên chậm chạp vì nó thực hiện nhiều thao tác xử lý phụ (như hiển thị prompt, xử lý history, v.v., dù không cần thiết trong môi trường thi đấu).
Trong những trường hợp này, module sys
cung cấp một giải pháp thay thế hiệu quả hơn nhiều: sys.stdin.readline
.
So sánh input()
và sys.stdin.readline()
:
input()
: Đọc một dòng, loại bỏ ký tự xuống dòng\n
ở cuối. Chậm hơn.sys.stdin.readline()
: Đọc một dòng, giữ lại ký tự xuống dòng\n
ở cuối (nếu có). Nhanh hơn đáng kể với input lớn.
Rất quan trọng: Vì sys.stdin.readline()
giữ lại \n
, bạn hầu như luôn luôn cần dùng thêm phương thức .strip()
để loại bỏ khoảng trắng thừa (bao gồm cả \n
) ở đầu và cuối chuỗi đọc được.
Ví dụ: Đọc N dòng, mỗi dòng một số nguyên (phiên bản tối ưu)
Input:
5
10
20
30
40
50
Code xử lý:
import sys
# Định nghĩa hàm đọc nhanh hơn để tiện sử dụng
def fast_input():
return sys.stdin.readline().strip()
n = int(fast_input()) # Đọc N
numbers = []
for _ in range(n):
num = int(fast_input()) # Đọc từng số
numbers.append(num)
print(f"Đã đọc {n} số (dùng sys.stdin): {numbers}")
# Phiên bản list comprehension với sys.stdin
n = int(fast_input())
numbers_comprehension = [int(fast_input()) for _ in range(n)]
print(f"Đã đọc (comprehension + sys.stdin) {n} số: {numbers_comprehension}")
Giải thích:
import sys
: Cần import modulesys
để sử dụng.sys.stdin.readline()
: Đọc một dòng từ standard input..strip()
: Loại bỏ ký tự\n
(và các khoảng trắng khác) ở cuối chuỗi trả về bởireadline()
. Đây là bước bắt buộc khi dùngreadline()
trong hầu hết các trường hợp thi đấu để tránh lỗi ép kiểu hoặc xử lý sai chuỗi.- Việc tạo một hàm
fast_input
hoặc gáninput = sys.stdin.readline
(cẩn thận!) có thể giúp code gọn hơn nếu bạn cần đọc nhiều lần.
Ví dụ: Đọc ma trận (phiên bản tối ưu)
Input:
2 3
1 2 3
4 5 6
Code xử lý:
import sys
input = sys.stdin.readline # Gán trực tiếp để dùng như input() cũ, nhưng nhớ strip()
R, C = map(int, input().split()) # input() bây giờ là sys.stdin.readline -> trả về "2 3\n"
# .split() hoạt động tốt với "\n" nên không cần strip() ở đây
matrix = []
for _ in range(R):
# Ở đây input().strip() là cần thiết trước khi split nếu dòng có thể có khoảng trắng thừa
# Tuy nhiên, split() thường xử lý tốt các khoảng trắng, nhưng cẩn thận vẫn hơn
row = list(map(int, input().strip().split()))
matrix.append(row)
print("Ma trận đọc bằng sys.stdin:")
for r in matrix:
print(r)
# Phiên bản list comprehension tối ưu
R, C = map(int, sys.stdin.readline().split()) # Đọc lại kích thước
matrix_comprehension = [list(map(int, sys.stdin.readline().strip().split())) for _ in range(R)]
print("Ma trận đọc bằng sys.stdin (comprehension):")
for r in matrix_comprehension:
print(r)
Giải thích:
- Khi gán
input = sys.stdin.readline
, bạn có thể dùnginput()
như cũ, nhưng phải nhớ rằng nó trả về chuỗi có\n
ở cuối. - Trong dòng đọc
R, C
,.split()
có thể xử lý chuỗi"2 3\n"
thành['2', '3']
mà không cần.strip()
trước. - Tuy nhiên, khi đọc các hàng của ma trận
input().strip().split()
, việc thêm.strip()
là an toàn hơn để loại bỏ\n
trước khi.split()
, đảm bảo không có phần tử rỗng không mong muốn nếu dòng input có định dạng lạ.
4. Đọc đến hết Input (End-of-File - EOF)
Một số bài toán không cho biết trước số lượng dòng input mà yêu cầu đọc cho đến khi không còn dữ liệu nữa (EOF). Điều này ít phổ biến hơn trong các kỳ thi online judge hiện đại (thường cho N rõ ràng), nhưng vẫn có thể gặp.
Cách 1: Dùng vòng lặp while True
và try-except
import sys
data = []
while True:
try:
line = input() # Hoặc sys.stdin.readline().strip() nếu cần tốc độ
# Xử lý 'line' ở đây, ví dụ:
# parts = list(map(int, line.split()))
# data.append(parts)
data.append(line) # Ví dụ đơn giản: lưu lại dòng
except EOFError: # Khi không còn dòng nào để đọc, input() sẽ raise EOFError
break # Thoát khỏi vòng lặp
print("Đã đọc đến EOF:")
for item in data:
print(item)
Cách 2: Lặp trực tiếp qua sys.stdin
(Thường dùng và gọn hơn)
import sys
data = []
for line in sys.stdin: # Tự động lặp qua từng dòng cho đến EOF
line = line.strip() # Nhớ strip() vì 'line' sẽ chứa '\n'
if not line: # Bỏ qua các dòng trống nếu cần
continue
# Xử lý 'line' ở đây
# parts = list(map(int, line.split()))
# data.append(parts)
data.append(line)
print("Đã đọc đến EOF (cách 2):")
for item in data:
print(item)
Giải thích:
- Cách 1 bắt lỗi
EOFError
xảy ra khiinput()
không thể đọc thêm. - Cách 2 thanh lịch hơn, vòng lặp
for line in sys.stdin
tự động dừng khi hết input. Lưu ý rằngline
trong cách này cũng chứa ký tự\n
, nên.strip()
là cần thiết.
5. Một số ví dụ tổng hợp và mẹo
Ví dụ: Bài toán có nhiều Test Case
Rất phổ biến trong thi đấu: dòng đầu tiên chứa số lượng test case T
, sau đó là T
khối dữ liệu, mỗi khối có định dạng riêng.
Input:
2
3
1 5
10 20
-5 0
2
100 200
300 400
Giải thích Input:
T = 2
(có 2 test case).- Test case 1:
N = 3
, sau đó là 3 dòng, mỗi dòng 2 số nguyên. - Test case 2:
N = 2
, sau đó là 2 dòng, mỗi dòng 2 số nguyên.
Code xử lý:
import sys
input = sys.stdin.readline # Ưu tiên tốc độ
T = int(input()) # Đọc số lượng test case
for t in range(1, T + 1): # Lặp qua từng test case
N = int(input()) # Đọc N cho test case hiện tại
pairs = []
for _ in range(N):
pair = list(map(int, input().strip().split()))
pairs.append(pair)
# --- Tại đây, bạn có biến N và list 'pairs' cho test case thứ t ---
# --- Thêm logic giải bài toán vào đây ---
result = 0 # Ví dụ: tính tổng tất cả các số
for p in pairs:
result += sum(p)
# In kết quả theo format yêu cầu (ví dụ: "Case #t: result")
print(f"Case #{t}: {result}")
Mẹo và Lưu ý quan trọng:
- Luôn đọc kỹ định dạng input: Sai một chi tiết nhỏ (khoảng trắng, dòng trống, thứ tự) có thể dẫn đến lỗi WA (Wrong Answer) hoặc RE (Runtime Error).
- Ưu tiên
sys.stdin.readline().strip()
: Trong môi trường thi đấu, đây gần như là lựa chọn mặc định để đảm bảo tốc độ, đặc biệt với Python. Hãy tập thói quen sử dụng nó. - *Sử dụng
map()
vàlist comprehension
: Chúng giúp code ngắn gọn, dễ đọc và thường hiệu quả hơn vòng lặpfor
truyền thống cho các tác vụ xử lý input đơn giản. - Ép kiểu cẩn thận: Nhớ dùng
int()
,float()
khi cần chuyển đổi từ chuỗi sang số. - Kiểm tra ràng buộc (constraints): Đề bài thường cho biết giới hạn tối đa của
N
, giá trị các số,... Điều này giúp bạn ước lượng lượng dữ liệu và quyết định xem có cần tối ưu bằngsys.stdin
hay không. NếuN
nhỏ (vài nghìn),input()
vẫn có thể chấp nhận được. - Thử nghiệm với input mẫu: Chạy code của bạn với các ví dụ input được cung cấp trong đề bài để đảm bảo nó đọc đúng.
Việc thành thạo các kỹ thuật xử lý input này là bước đệm vững chắc giúp bạn tập trung vào phần thú vị hơn: thiết kế thuật toán. Hãy thực hành thường xuyên với các bài tập trên các nền tảng như Codeforces, AtCoder, LeetCode,... để kỹ năng này trở thành phản xạ tự nhiên! Chúc các bạn code vui và hiệu quả!
Comments