Tuyển sinh 🎓 Mình nhận đào tạo lập trình giải thuật thi đấu: thi vào lớp 10 chuyên tin · thi học sinh giỏi tin học cấp tỉnh/thành · thi vào đội tuyển quốc gia · Olympic tin học sinh viên / ICPC · phỏng vấn thuật toán. Bấm vào đây để xem thông tin khóa học → Xem khóa học
Thi thử miễn phí
🏆 Thi thử HSG Tin học tuần này trên ClueOJ
Mở công khai cho mọi người · đề do thầy Nguyễn Việt Nam Sơn biên soạn
Lịch thi, đề cũ & lời giải →

Chuyên đề 7: Đọc ghi file và nhập xuất nhanh trong C++ và Python: freopen, đọc tới hết file, getline, định dạng output và những lỗi làm bài đúng mà 0 điểm

Chuyên đề 7: Đọc ghi file và nhập xuất nhanh trong C++ và Python: freopen, đọc tới hết file, getline, định dạng output và những lỗi làm bài đúng mà 0 điểm
Series: Chuyên đề nhập môn Độ khó: Dễ Chuyên đề 7 / Nhập môn lập trình Đọc ghi file Nhập xuất
// Chuyên đề nhập môn · Chuyên đề 7
Đọc ghi file và nhập xuất nhanh: những lỗi làm bài đúng mà 0 điểm
Thuật toán đúng, chạy thử trên máy ra kết quả đúng, vậy mà chấm xong được 0 điểm. Thủ phạm hầu như luôn là tên file, cách đọc dữ liệu, một dấu cách thừa hay một chữ endl. Bài này giúp em không bao giờ mất điểm oan như thế.

Lời mở đầu

Năm nào thầy cũng nghe vài câu chuyện giống hệt nhau sau kỳ thi HSG: “Em làm được 3 bài mà chỉ được 5 điểm”, “Bài của em chạy đúng mà máy chấm cho 0”. Mở bài làm ra xem thì thấy: file tên bai1.cpp.txt, quên freopen nên in ra màn hình, đọc nhầm dòng vì getline, in thêm câu “Ket qua la:”, hoặc dùng endl in một triệu dòng nên quá thời gian. Không lỗi nào liên quan tới thuật toán, nhưng mỗi lỗi đều đủ để mất trắng cả bài.

Ở bài 9 em đã biết máy chấm làm việc thế nào, cách dùng freopen cơ bản và mẹo tăng tốc nhập xuất; ở bài 2 em đã quen cin, cout. Chuyên đề này không giảng lại những thứ đó mà đi thẳng vào thực chiến: quy định nộp bài trong phòng thi, mọi dạng input hay gặp trong đề, định dạng output sao cho máy chấm nào cũng chịu, đọc ghi thật nhanh khi dữ liệu lớn, và một bảng kiểm tra trước khi nộp. Phần kỹ năng sinh test, chấm trâu em sẽ gặp lại kỹ hơn ở bài 22: Kỹ năng luyện thi.

Trong bài này bạn sẽ học: quy định nộp bài trong kỳ thi HSG: tên file, thư mục, BAI1.INP/BAI1.OUT, đặt freopen đúng chỗ và mẹo một bài nộp chạy được cả trên máy chấm lẫn web luyện tập; năm dạng input: biết trước $n$, nhiều bộ test, đọc tới hết file, đọc theo dòng với getline (và cái bẫy dòng rỗng, ký tự \r), mỗi dòng không biết trước bao nhiêu số; định dạng output: dấu cách, xuống dòng, số thực, dấu phân cách; nhập xuất nhanh với sync_with_stdio, cin.tie, '\n', scanf/printf, Python đọc một lần in một lần, và cách đo thời gian; bảng kiểm tra trước khi nộp và cách tự tạo file test. Có khung chạy tay, 3 mô phỏng bấm từng bước và 8 bài tập kèm gợi ý.

Lộ trình bài viết, bấm vào từng phần để nhảy tới:

Phần 1Quy định nộp bài trong kỳ thi HSG

Ở kỳ thi HSG cấp tỉnh, thành phố, bài của em thường được chấm tự động bằng phần mềm (Themis, CMS hoặc một hệ thống tương tự). Máy chấm không đọc code, không hiểu ý em, nó chỉ làm đúng ba việc với từng test: đặt file dữ liệu vào cạnh chương trình, chạy chương trình trong giới hạn thời gian, rồi so file kết quả với đáp án. Đề thi luôn có một bảng như sau, em phải đọc thật kỹ trước khi viết dòng code đầu tiên:

BàiTên file chương trìnhFile dữ liệu vàoFile kết quảThời gian
Bài 1BAI1.CPP hoặc BAI1.PYBAI1.INPBAI1.OUT1 giây
Bài 2DAYSO.CPP hoặc DAYSO.PYDAYSO.INPDAYSO.OUT1 giây
Bài 3LUOI.CPP hoặc LUOI.PYLUOI.INPLUOI.OUT2 giây

Bảng 1. Bảng quy định tên file điển hình của một đề HSG. Tên chương trình, tên file vào và file ra phải giống hệt từng chữ cái.

TS012BAI1.CPPđúng tên đề choBAI2.CPPBAI3.PYbai1.cpp.txtsai: đuôi .txt ẩnBai 1.cppsai: dấu cáchBAI1.INPtest số kBAI1.EXEbài của emBAI1.OUTem ghi raĐÁP ÁNcủa người ra đềso sánhkhớp → điểm test klệch 1 ký tự → 0 điểmLặp lại cho mọi test; quá thời gian cũng 0 điểm test đó.

Hình 1. Bên trái: thư mục bài làm của thí sinh (thường đặt tên theo số báo danh), chỉ chứa file nguồn đúng tên. Bên phải: với mỗi test, máy chấm chạy chương trình, đọc BAI1.OUT em ghi ra rồi so với đáp án.

Năm quy tắc vàng khi nộp bài:

  1. Tên file nguồn đúng như đề: BAI1.CPP, không phải Bai1.cpp, bai 1.cpp hay BAI1.cpp.cpp. Máy chấm chạy trên Linux phân biệt chữ hoa, chữ thường; Windows thì không, nên em chạy trên máy mình thấy đúng mà lên máy chấm lại không tìm thấy file. Cách an toàn duy nhất: chép nguyên văn tên trong đề.
  2. freopen đúng tên, đúng chế độ, đúng chỗ: "r" cho file vào, "w" cho file ra, đặt ngay đầu hàm main, trước mọi lệnh đọc ghi. Chỉ ghi tên file, không ghi đường dẫn như "D:\\BAITHI\\BAI1.INP", vì máy chấm đặt file ở thư mục của nó.
  3. Không in lời nhắc: không có cout << "Nhap n: ", không có input("Nhap n: "), không có “Ket qua la”. Mọi chữ in ra đều bị đem đi so với đáp án.
  4. Không dừng màn hình: bỏ system("pause"), getch(), #include <conio.h>. Máy chấm không có ai bấm phím, chương trình sẽ đứng chờ tới khi quá thời gian. Kết thúc bằng return 0;.
  5. Mỗi bài một file, nộp đúng thư mục: không để file .exe, không để thư mục con lồng nhau, không nộp nhầm bản nháp.
Hai cú lừa của Windows
1. Đuôi file bị ẩn. Windows mặc định giấu phần đuôi, nên khi em tạo file BAI1.INP bằng Notepad, tên thật có thể là BAI1.INP.txt. Chương trình mở BAI1.INP không thấy gì, đọc ra toàn số 0. Hãy bật hiện đuôi file (File Explorer → View → File name extensions) ngay khi vào phòng thi, và khi lưu bằng Notepad chọn “Save as type: All files”.
2. Ghi nhầm chế độ. Gõ nhầm freopen("BAI1.INP", "w", stdin) sẽ xoá sạch file dữ liệu ngay khi chạy. Em nên chép khung chuẩn bên dưới chứ đừng gõ tay mỗi lần.

Một bài nộp chạy được ở mọi nơi. Khi luyện trên web chấm (Codeforces, VNOJ, CLUE OJ…), dữ liệu đi qua bàn phím và màn hình (stdin, stdout), không có file. Còn trong phòng thi lại bắt buộc dùng file. Thay vì nhớ thêm, bớt freopen mỗi lần (rất dễ quên!), em dùng mẹo: thử mở file trước, mở được thì mới chuyển hướng. Trên máy chấm có BAI1.INP nên chương trình đọc ghi file; trên web không có file nên vẫn dùng stdin, stdout bình thường. Đây là khung thầy khuyên em dùng cho mọi bài:

Xem code C++ / Python: khung bài nộp chuẩn: đọc ghi file khi có file, ngược lại dùng bàn phím (bấm để mở)
#include <bits/stdc++.h>
using namespace std;

int main(){
    // Có file BAI1.INP thì đọc ghi file, không có thì dùng bàn phím và màn hình
    if(fopen("BAI1.INP", "r")){
        freopen("BAI1.INP", "r", stdin);
        freopen("BAI1.OUT", "w", stdout);
    }
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n;
    cin >> n;                        // KHÔNG in "Nhap n:" trước khi đọc
    long long s = 0;
    for(int i = 0; i < n; i++){
        long long x;
        cin >> x;
        s += x;
    }
    cout << s << "\n";               // chỉ in đúng thứ đề yêu cầu
    return 0;                        // KHÔNG system("pause"), KHÔNG getch()
}
import sys, os

# Có file BAI1.INP thì đọc ghi file, không có thì dùng bàn phím và màn hình
if os.path.exists('BAI1.INP'):
    sys.stdin = open('BAI1.INP', 'r')
    sys.stdout = open('BAI1.OUT', 'w')

data = sys.stdin.read().split()      # KHÔNG dùng input("Nhap n: ")
n = int(data[0])
s = sum(map(int, data[1:n + 1]))
print(s)                             # chỉ in đúng thứ đề yêu cầu
sys.stdout.flush()                   # đảm bảo dữ liệu đã ghi hết ra file
Khuôn mẫu đầu hàm main (C++)
int main(){
    if(fopen("BAI1.INP", "r")){                 // tên lấy NGUYÊN VĂN từ đề
        freopen("BAI1.INP", "r", stdin);       // "r": đọc
        freopen("BAI1.OUT", "w", stdout);      // "w": ghi
    }
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    // ... đọc, xử lý, in ...
    return 0;
}
Đổi đúng 3 chỗ BAI1 thành tên bài trong đề. Với Python, thay bằng os.path.exists và gán lại sys.stdin, sys.stdout như code ở trên. Khi đã chuyển sys.stdout sang file, dùng print như bình thường; trước khi thoát nên gọi sys.stdout.flush() cho chắc.

In thông tin gỡ lỗi thế nào cho an toàn? Trong lúc làm bài em hay in thêm giá trị biến để kiểm tra. Nếu in bằng cout/print thì nó lẫn vào kết quả, quên xoá là sai. Hãy in ra luồng lỗi: cerr << "x = " << x << "\n"; trong C++, print("x =", x, file=sys.stderr) trong Python. Luồng lỗi hiện lên màn hình khi em chạy thử nhưng không bị ghi vào file kết quả. Dù vậy, trước khi nộp vẫn nên xoá đi vì in nhiều cũng làm chậm chương trình.

Phần 2Các dạng input hay gặp và cách đọc từng dạng

Điều quan trọng nhất cần hiểu: với chương trình, dữ liệu vào chỉ là một dãy ký tự liên tiếp, có một con trỏ đọc chạy từ đầu tới cuối. Dấu xuống dòng cũng chỉ là một ký tự ('\n'). Mỗi cách đọc di chuyển con trỏ theo một luật riêng:

  • cin >> x (và scanf): bỏ qua mọi khoảng trắng (dấu cách, tab, xuống dòng) đứng trước, đọc một “từ”, rồi dừng ngay sau ký tự cuối của từ đó. Ký tự xuống dòng phía sau vẫn nằm nguyên.
  • getline(cin, s): đọc mọi thứ từ con trỏ tới hết dòng (kể cả dấu cách), nuốt luôn ký tự xuống dòng nhưng không đưa vào s.
  • Python input(): đọc một dòng, bỏ '\n' ở cuối. sys.stdin.read().split(): đọc hết rồi tách theo mọi khoảng trắng, xuống dòng ở đâu cũng không quan trọng.

Em xem con trỏ chạy trong mô phỏng dưới đây. Chương trình mặc định là cái bẫy kinh điển: đọc n bằng cin rồi đọc tên bằng getline. Sau đó em thử chương trình thứ ba (cách sửa) và chế độ xuống dòng kiểu Windows:

MÔ PHỎNGCon trỏ đọc chạy trên dữ liệu vào: cin >> và getlineSửa dữ liệu vào, chọn chương trình rồi bấm Tiếp. Ô xanh dương là con trỏ đọc, ô vàng là phần vừa đọc vào biến.
con trỏ đọcvừa đọc vào biếnký tự trắng bị bỏ qua / nuốtđã đọc qua

Giờ ta đi qua năm dạng input mà đề thi hay dùng. Bảng sau là “bản đồ” để em nhận dạng nhanh:

DạngĐề viết kiểuC++Python
1. Biết trước số lượng“Dòng đầu là $n$, dòng sau gồm $n$ số”cin >> n; rồi for $n$ lầndata = sys.stdin.read().split()
2. Nhiều bộ test“Dòng đầu là $T$, tiếp theo là $T$ bộ dữ liệu”for(t = 1..T), đặt lại biến mỗi bộđọc hết, dùng chỉ số p chạy dần
3. Tới hết file“Gồm nhiều dòng, mỗi dòng…” (không cho số lượng)while(cin >> x)for x in data
4. Theo dòng, có dấu cách“Mỗi dòng là họ tên / một câu văn”getline, nhớ bỏ phần còn lại của dòng trước.split('\n'), rstrip('\r')
5. Mỗi dòng không biết trước bao nhiêu số“Dòng thứ $i$ gồm các số của nhóm $i$”getline + stringstreamline.split()

Bảng 2. Năm dạng input. Dạng 1 em đã quen từ bài 2; phần này tập trung vào bốn dạng còn lại.

Dạng 2: nhiều bộ test trong một file

Đề cho $T$ bộ dữ liệu liên tiếp, yêu cầu in kết quả mỗi bộ trên một dòng, thường kèm định dạng như Case #1: …. Hai lỗi kinh điển: quên đặt lại biến (tổng, mảng đếm, cờ đánh dấu) khi sang bộ mới, và dừng đọc giữa chừng (ví dụ tìm thấy đáp án rồi break khỏi vòng đọc), làm số còn lại của bộ này bị đọc nhầm sang bộ sau. Quy tắc: luôn đọc hết dữ liệu của bộ test trước, xử lý sau; và khai báo biến bên trong vòng lặp test để nó tự được tạo mới. Em chạy tay chương trình dưới đây, để ý dòng s = 0:

C++▶ Chạy tay từng dòng codeNhiều bộ test: mỗi bộ đọc n số, in “Case #t: tổng”. Biến s được tạo lại cho từng bộ.
Dữ liệu mẫu:
1#include <bits/stdc++.h>
2using namespace std;
3
4int main(){
5 int T;
6 cin >> T;
7 for(int t = 1; t <= T; t++){
8 int n;
9 cin >> n;
10 long long s = 0;
11 for(int i = 0; i < n; i++){
12 long long x;
13 cin >> x;
14 s += x;
15 }
16 cout << "Case #" << t << ": " << s << "\n";
17 }
18 return 0;
19}
Mảng lớn và nhiều bộ test
Nếu mỗi bộ dùng một mảng đếm cỡ $10^6$ mà $T = 10^4$, đặt lại cả mảng bằng memset mỗi bộ là $10^{10}$ phép, quá thời gian. Hãy chỉ xoá đúng những ô đã dùng (duyệt lại các số của bộ test và đặt cnt[a[i]] = 0), hoặc dùng vector có kích thước $n$ của chính bộ đó. Đề thường cho thêm điều kiện “tổng $n$ của mọi bộ không quá $2 \cdot 10^5$” chính là để em làm như vậy.

Dạng 3: đọc tới hết file

Khi đề không cho số lượng (“file gồm nhiều số nguyên”, “mỗi dòng một cặp số, không biết có bao nhiêu dòng”), ta đọc cho tới khi hết dữ liệu. Trong C++, biểu thức cin >> x trả về “đúng” nếu đọc được, “sai” nếu hết dữ liệu hoặc gặp ký tự không phải số, nên đặt thẳng vào while. Cách đọc từng cặp: while(cin >> a >> b). Python thì đơn giản nhất là đọc hết rồi tách. (Khung chạy tay “đọc số tới khi hết dữ liệu” em đã gặp ở bài 9.)

Xem code C++ / Python: đọc số tới hết file, in số lượng, tổng và số lớn nhất (bấm để mở)
#include <bits/stdc++.h>
using namespace std;

int main(){
    ios::sync_with_stdio(false); cin.tie(nullptr);
    long long x, s = 0, mx = LLONG_MIN;
    int cnt = 0;
    while(cin >> x){                 // đọc được thì làm, hết dữ liệu thì dừng
        cnt++;
        s += x;
        mx = max(mx, x);
    }
    cout << cnt << " " << s << " " << mx << "\n";
    return 0;
}
import sys

a = list(map(int, sys.stdin.buffer.read().split()))   # đọc hết, tách theo mọi khoảng trắng
print(len(a), sum(a), max(a))
Đừng dùng while(!cin.eof())
Nhiều bạn viết while(!cin.eof()){ cin >> x; ... }. Nếu file kết thúc bằng một dấu xuống dòng (gần như luôn như vậy), sau khi đọc số cuối, eof() vẫn chưa đúng vì còn ký tự '\n'; vòng lặp chạy thêm một lần, cin >> x thất bại nhưng em vẫn xử lý x → số cuối bị tính hai lần. Luôn đặt chính lệnh đọc làm điều kiện của while. Tương tự trong Python: đừng gọi input() trong while True mà không bắt lỗi hết dữ liệu; hãy đọc hết bằng sys.stdin.read().

Một biến thể rất hay gặp trong đề cũ: dữ liệu kết thúc bằng một dòng đặc biệt như 0 0 hay -1 (gọi là lính canh). Khi đó em đọc từng cặp, gặp lính canh thì break ngay, không xử lý nó và không đọc gì thêm, vì phía sau có thể còn dữ liệu rác (bài tập 2).

Dạng 4: đọc theo dòng, dòng có dấu cách

Họ tên, câu văn, địa chỉ… chứa dấu cách nên cin >> s chỉ đọc được một từ. Phải dùng getline. Nhưng như mô phỏng ở trên cho thấy, sau cin >> n con trỏ dừng trước ký tự xuống dòng, nên getline đầu tiên đọc ra xâu rỗng. Hai cách sửa:

  • cin.ignore(numeric_limits<streamsize>::max(), '\n'); bỏ mọi thứ tới hết dòng (kể cả dấu cách thừa sau $n$). Dùng cin.ignore() không tham số chỉ bỏ một ký tự, sẽ hỏng nếu sau $n$ còn dấu cách.
  • cin >> n >> ws; bỏ mọi khoảng trắng tới ký tự thật đầu tiên. Gọn, nhưng nếu dòng tiếp theo cố tình là dòng trống hoặc bắt đầu bằng dấu cách có ý nghĩa thì ws nuốt mất.

Ký tự '\r' từ Windows. Trên Windows, xuống dòng là hai ký tự \r\n; trên Linux chỉ là \n. Nếu file test được soạn trên Windows mà máy chấm chạy Linux, getline trả về xâu còn dính '\r' ở cuối: in ra nhìn thì giống, nhưng s.size() lệch 1, so sánh s == "YES" thành sai. Python cũng vậy: input() trên Linux trả về 'abc\r'. Thói quen tốt: sau mỗi lần đọc dòng, xoá '\r' ở cuối nếu có. cin >> và split() thì không bị ảnh hưởng vì coi '\r' là khoảng trắng.

Xem code C++ / Python: đọc n rồi n dòng họ tên có dấu cách (an toàn với \r) (bấm để mở)
#include <bits/stdc++.h>
using namespace std;

int main(){
    int n;
    cin >> n;
    cin.ignore(numeric_limits<streamsize>::max(), '\n');   // bỏ phần còn lại của dòng đầu (kể cả '\n')
    for(int i = 1; i <= n; i++){
        string s;
        getline(cin, s);
        if(!s.empty() && s.back() == '\r') s.pop_back();      // file soạn trên Windows: xoá '\r'
        cout << i << ". " << s << " (" << s.size() << ")\n";
    }
    return 0;
}
import sys

lines = sys.stdin.buffer.read().decode().split('\n')
n = int(lines[0])
for i in range(1, n + 1):
    s = lines[i].rstrip('\r')                 # file soạn trên Windows: xoá '\r'
    print(f"{i}. {s} ({len(s)})")

Dạng 5: mỗi dòng không biết trước bao nhiêu số

Ví dụ: “Mỗi dòng ghi điểm các bài kiểm tra của một học sinh, số bài của mỗi bạn khác nhau”. Nếu đọc bằng cin >> x liên tục, em không biết số nào thuộc dòng nào vì cin coi xuống dòng như dấu cách. Cách làm: đọc cả dòng bằng getline, rồi biến xâu đó thành một luồng nhỏ stringstream và đọc số từ luồng nhỏ cho tới khi hết dòng. Python chỉ cần line.split(). Đừng quên dòng trống (học sinh không có bài nào) vẫn là một dòng hợp lệ.

Xem code C++ / Python: mỗi dòng một nhóm số: in số lượng và tổng của từng dòng (bấm để mở)
#include <bits/stdc++.h>
using namespace std;

int main(){
    string line;
    while(getline(cin, line)){                 // mỗi dòng là một nhóm, không biết trước có mấy số
        stringstream ss(line);                 // biến dòng thành một "luồng nhỏ"
        long long x, s = 0;
        int cnt = 0;
        while(ss >> x){ cnt++; s += x; }       // đọc tới hết dòng
        cout << cnt << " " << s << "\n";
    }
    return 0;
}
import sys

out = []
for line in sys.stdin.buffer.read().decode().splitlines():   # mỗi dòng là một nhóm
    a = list(map(int, line.split()))                          # split() tự bỏ '\r', dấu cách thừa
    out.append(f"{len(a)} {sum(a)}")
print('\n'.join(out))

Phần 3Định dạng output: máy chấm so từng ký tự

Sau khi chạy xong, máy chấm so file kết quả của em với đáp án. Mỗi hệ thống có một kiểu so: có nơi so tuyệt đối từng ký tự, có nơi bỏ qua dấu cách thừa cuối dòng và dòng trống cuối file, có nơi (như phần lớn bài trên Codeforces) chỉ so từng số, từng từ. Em không được trông chờ vào kiểu dễ tính: hãy viết output sao cho qua được cả kiểu khắt khe nhất. Mô phỏng dưới đây cho em thấy cùng một output có thể AC ở máy chấm này mà WA ở máy chấm khác:

MÔ PHỎNGMáy chấm so output của em với đáp ánSửa hai ô output (␣ là dấu cách), chọn kiểu chấm rồi bấm Tiếp để xem máy chấm so từng dòng.
đang sođã khớpchỗ sai khác

Một số chỗ em nên thử trong mô phỏng: xoá dấu cách cuối dòng 2 của “Output của em”; gõ Ket qua: 6 vào dòng 1 (thừa lời nhắc); viết 61 2 3 trên một dòng (quên xuống dòng); thêm một dòng trống ở cuối. Những quy tắc rút ra:

Đề yêu cầuViết đúng (C++)Viết đúng (Python)Lỗi hay gặp
Các số trên một dòng, cách nhau một dấu cáchcout << a[i] << (i + 1 < n ? " " : "\n");print(*a)quên xuống dòng sau dòng cuối, in dính số: 123
Mỗi số một dòngcout << x << '\n';print('\n'.join(map(str, a)))dùng endl khi in nhiều (chậm, xem phần 4)
Cách nhau dấu phẩy: 1, 2, 3in ", " trước mọi số trừ số đầuprint(*a, sep=', ')thừa dấu phẩy cuối: 1, 2, 3,
Số thực, đúng 2 chữ số sau dấu chấmcout << fixed << setprecision(2) << x;print(f"{x:.2f}")quên fixed nên in 1.2e+02 hoặc 3.1
YES / NOin đúng chữ hoa như đềnhư C++in Yes, yes, CO
Định dạng có chữ: Case #1: 5chép nguyên văn từ đềf-string: f"Case #{t}: {s}"Case 1: 5, case #1:5
Ma trận $n \times m$mỗi hàng một dòngprint(' '.join(map(str, row)))in cả ma trận trên một dòng; in theo cột

Bảng 3. Các định dạng output thường gặp. Cột cuối là những lỗi thầy gặp nhiều nhất khi chấm bài học sinh.

Kỹ thuật “in dấu phân cách trước”. Muốn các số cách nhau bởi một dấu hiệu (dấu cách, dấu phẩy, dấu +…) mà không thừa ở cuối, cách chắc chắn nhất là: với mọi phần tử trừ phần tử đầu, in dấu phân cách rồi mới in phần tử. Không cần biết phần tử nào là cuối, nên dùng được cả khi đang duyệt và lọc (không biết trước sẽ in bao nhiêu số). Chạy tay để thấy dấu phẩy luôn nằm giữa hai số:

C++▶ Chạy tay từng dòng codeIn dãy dạng [a1, a2, ..., an]: dấu phân cách được in TRƯỚC mỗi phần tử, trừ phần tử đầu tiên.
Dữ liệu mẫu:
1#include <bits/stdc++.h>
2using namespace std;
3
4int main(){
5 int n;
6 cin >> n;
7 vector<int> a(n);
8 for(int i = 0; i < n; i++) cin >> a[i];
9 cout << "[";
10 for(int i = 0; i < n; i++){
11 if(i > 0) cout << ", ";
12 cout << a[i];
13 }
14 cout << "]\n";
15 return 0;
16}

Code dưới đây gom ba kiểu in hay gặp nhất: dãy cách nhau một dấu cách, dãy có ngoặc và dấu phẩy, số thực đúng 2 chữ số sau dấu chấm. Chuyện làm tròn, sai số số thực em đã học kỹ ở chuyên đề 2; ở đây chỉ cần nhớ: muốn đúng k chữ số sau dấu chấm thì C++ phải có cả fixed và setprecision(k), Python dùng f"{x:.kf}".

Xem code C++ / Python: in dãy theo ba định dạng và số thực 2 chữ số thập phân (bấm để mở)
#include <bits/stdc++.h>
using namespace std;

int main(){
    int n;
    cin >> n;
    vector<long long> a(n);
    long long s = 0;
    for(auto &x : a){ cin >> x; s += x; }
    // Dòng 1: cách nhau một dấu cách, không thừa dấu cách cuối dòng
    for(int i = 0; i < n; i++) cout << a[i] << (i + 1 < n ? " " : "\n");
    // Dòng 2: dạng [a1, a2, ..., an]
    cout << "[";
    for(int i = 0; i < n; i++){
        if(i > 0) cout << ", ";
        cout << a[i];
    }
    cout << "]\n";
    // Dòng 3: trung bình cộng, đúng 2 chữ số sau dấu chấm
    cout << fixed << setprecision(2) << (double)s / n << "\n";
    return 0;
}
import sys

data = sys.stdin.read().split()
n = int(data[0])
a = list(map(int, data[1:n + 1]))
print(*a)                                   # cách nhau một dấu cách (mặc định sep=' ')
print('[' + ', '.join(map(str, a)) + ']')   # hoặc print('[', end=''); print(*a, sep=', ', end=']\n')
print(f"{sum(a) / n:.2f}")                  # đúng 2 chữ số sau dấu chấm
Dấu cách cuối dòng và dòng trống cuối file
Phần lớn máy chấm (kể cả Themis với cấu hình thường dùng) bỏ qua dấu cách thừa cuối dòng và xuống dòng cuối file, nên code kiểu cout << a[i] << " " vẫn thường được chấp nhận. Nhưng nếu đề viết “không có dấu cách thừa” hoặc dùng trình so khớp tuyệt đối, em sẽ mất điểm. Viết sạch ngay từ đầu tốn thêm đúng một dấu ?:, rẻ hơn rất nhiều so với rủi ro. Ngược lại, luôn kết thúc dòng cuối bằng \n: thiếu nó gần như không bao giờ bị trừ điểm, còn thừa dòng trống ở giữa thì luôn sai.

Phần 4Nhập xuất nhanh: khi đọc ghi cũng có thể quá thời gian

Với $n = 10^6$ số, chỉ riêng việc đọc vào bằng cin mặc định đã mất cỡ 0,3 giây, in ra một triệu dòng bằng endl có thể mất cả giây: thuật toán $O(n)$ mà vẫn TLE! Lý do nằm ở ba chỗ, mỗi chỗ sửa bằng một dòng code:

  • ios::sync_with_stdio(false); Mặc định, cin/cout phải “đồng bộ” với scanf/printf của C để em trộn chung cũng không lộn xộn, cái giá là chậm. Tắt đồng bộ thì cin nhanh ngang scanf. Đổi lại, từ đó không được trộn cin/cout với scanf/printf/puts trong cùng chương trình.
  • cin.tie(nullptr); Mặc định cin bị “buộc” với cout: trước mỗi lần đọc, cout bị xả bộ đệm (để người dùng kịp thấy lời nhắc “Nhap n:”). Khi chấm bài không có ai đọc lời nhắc, cởi dây buộc ra.
  • '\n' thay cho endl. endl = xuống dòng và xả bộ đệm. Mỗi lần xả là một lần nhờ hệ điều hành ghi ra đĩa, chậm hơn hàng trăm lần so với chép chữ vào bộ nhớ.

Bộ đệm là gì và “xả” là gì thì nhìn là hiểu ngay. Em chọn lần lượt ba cách in rồi đếm số lần xả, bước cuối có ước lượng thời gian cho một triệu dòng:

MÔ PHỎNGBộ đệm đầu ra: endl, '\n' và cin.tie(0)Nhập vài số, chọn cách in rồi bấm Tiếp. Bước cuối ước lượng thời gian khi in N dòng.
số đang xử lý / chữ vừa ghi ra filechữ nằm trong bộ đệmđã nằm trong file
Xem code C++ / Python: nhập xuất nhanh với cin/cout và Python đọc một lần, in một lần (bấm để mở)
#include <bits/stdc++.h>
using namespace std;

int main(){
    ios::sync_with_stdio(false);    // tách cin/cout khỏi scanf/printf: nhanh hơn nhiều
    cin.tie(nullptr);               // cin không bắt cout xả bộ đệm trước mỗi lần đọc
    int n;
    cin >> n;
    long long s = 0;
    for(int i = 0; i < n; i++){
        long long x;
        cin >> x;
        s += x;
        cout << s << '\n';          // '\n' chứ KHÔNG dùng endl
    }
    return 0;
}
import sys

def main():
    data = sys.stdin.buffer.read().split()    # đọc MỘT lần toàn bộ dữ liệu
    n = int(data[0])
    out = []
    s = 0
    for i in range(1, n + 1):
        s += int(data[i])
        out.append(s)
    sys.stdout.write('\n'.join(map(str, out)) + '\n')   # in MỘT lần
main()

scanf/printf là lựa chọn nhanh khác cho C++, đặc biệt khi cần in số thực với định dạng phức tạp (printf("%.3f", x)) hoặc đọc dữ liệu có ký tự xen giữa như giờ 12:30 (scanf("%d:%d", &h, &m)). Nhớ đúng mã định dạng: %d cho int, %lld cho long long, %lf khi đọc double, %s cho mảng char. Sai mã định dạng không báo lỗi biên dịch mà cho kết quả rác.

Xem code C++ / Python: cùng bài với scanf/printf và itertools.accumulate (bấm để mở)
#include <bits/stdc++.h>
using namespace std;

int main(){
    int n;
    scanf("%d", &n);
    long long s = 0;
    for(int i = 0; i < n; i++){
        long long x;
        scanf("%lld", &x);           // long long đi với %lld
        s += x;
        printf("%lld\n", s);
    }
    return 0;                        // dùng scanf/printf thì KHÔNG trộn với cin/cout đã tắt sync
}
import sys
from itertools import accumulate

data = sys.stdin.buffer.read().split()
n = int(data[0])
print('\n'.join(map(str, accumulate(map(int, data[1:n + 1])))))

Python khi dữ liệu lớn. Mỗi lời gọi input() và print() đều có chi phí đáng kể. Với $10^5$ dòng trở lên, dùng ba thói quen sau (chuyên đề 8 sẽ bàn kỹ hơn về Python khi đi thi):

  • Đọc một lần: data = sys.stdin.buffer.read().split() rồi lấy dần bằng chỉ số. Nhanh gấp nhiều lần gọi input() từng dòng.
  • In một lần: gom kết quả vào list, cuối cùng sys.stdout.write('\n'.join(map(str, res)) + '\n').
  • Nếu thật sự phải đọc theo dòng: input = sys.stdin.readline (nhớ dòng đọc được còn dính '\n', dùng .split() hoặc .rstrip()).
Cách làm (đọc $10^6$ số, in $10^6$ dòng)C++Python
Mặc định, endl / input() + print() từng dòngkhoảng 1 – 2 giâykhoảng 2 – 4 giây
Tắt sync + cin.tie(0) nhưng vẫn endlkhoảng 0,5 – 1 giây–
Tắt sync + cin.tie(0) + '\n' / scanf, printfkhoảng 0,1 giây–
Python đọc một lần, in một lần–khoảng 0,5 giây

Bảng 4. Thời gian tham khảo trên một máy chấm thông thường. Số cụ thể tuỳ máy, nhưng thứ tự nhanh chậm thì không đổi.

Tự đo thời gian chạy
Đừng đoán, hãy đo. Tạo một file test lớn (phần 5), rồi bọc phần xử lý bằng đồng hồ và in thời gian ra luồng lỗi để không lẫn vào kết quả:
auto t0 = chrono::steady_clock::now();
// ... toàn bộ đọc, xử lý, in ...
double sec = chrono::duration<double>(chrono::steady_clock::now() - t0).count();
cerr << "Thoi gian: " << sec << " s\n";
import time, sys
t0 = time.perf_counter()
# ... toàn bộ đọc, xử lý, in ...
print("Thoi gian:", time.perf_counter() - t0, "s", file=sys.stderr)
Nếu chạy trên máy em đã gần giới hạn thời gian của đề thì trên máy chấm cũng nguy hiểm. Nhớ xoá đoạn đo trước khi nộp. Cách ước lượng số phép tính từ giới hạn đề em xem lại ở bài 9 và bài 22.

Phần 5Bảng kiểm tra trước khi nộp và cách tự tạo file test

Mười lăm phút cuối giờ thi là lúc dễ mất điểm oan nhất: sửa vội một chỗ, quên bật lại freopen, nộp nhầm file. Thầy luôn bắt học sinh dò lại từng bài theo bảng dưới đây, mất khoảng một phút một bài:

#Kiểm traCách kiểm tra nhanh
1Tên file nguồn đúng từng chữ, đúng đuôi, đúng thư mụcbật hiện đuôi file, so với bảng trong đề
2freopen / open đúng tên file vào, file ra; "r" và "w" không nhầmđọc lại 3 dòng đầu main
3Không còn lời nhắc, không còn dòng in gỡ lỗi ra cout/printtìm chữ cout, print trong code
4Không có system("pause"), getch(), conio.htìm chữ pause, conio
5Kiểu dữ liệu đủ lớn (tổng, tích dùng long long)nhân thử giá trị lớn nhất trong đề
6Định dạng output đúng mẫu: chữ hoa thường, dấu cách, số chữ số thập phânso ví dụ trong đề với output từng ký tự
7Chạy đúng ví dụ trong đề bằng file (không gõ tay)tạo BAI1.INP, chạy, mở BAI1.OUT
8Chạy được test biên: $n$ nhỏ nhất, $n$ lớn nhất, số âm, toàn số bằng nhautự tạo file test (bên dưới)
9Test lớn nhất chạy kịp thời giansinh test lớn, đo thời gian
10Biên dịch lại bản cuối cùng không lỗi, đúng chuẩn C++ của kỳ thibấm biên dịch lần cuối sau lần sửa cuối

Bảng 5. Mười điều cần dò trước khi nộp. In ra, dán cạnh máy khi luyện đề cho thành phản xạ.

Tự tạo file test bằng tay. Mở Notepad (hoặc chính Code::Blocks), gõ dữ liệu, lưu với tên BAI1.INP cùng thư mục với file nguồn. Đừng chỉ chép ví dụ trong đề: hãy viết thêm các test “khó chịu” mà đề cho phép: $n = 1$; mọi số bằng nhau; số âm lớn nhất; dòng trống ở cuối file; file không có dấu xuống dòng ở dòng cuối; nhiều dấu cách giữa các số. Mỗi test em tự tính đáp án bằng tay rồi so.

Sinh test lớn bằng chương trình. Test $n = 10^6$ thì không gõ tay được. Viết một chương trình nhỏ in ra dữ liệu ngẫu nhiên, rồi chuyển hướng output vào file: gen < thamso.txt > BAI1.INP (C++ đã biên dịch) hoặc python gen.py < thamso.txt > BAI1.INP. Dấu > trong cửa sổ lệnh có tác dụng y như freopen ra file. Code dưới đây tự viết bộ sinh số giả ngẫu nhiên đơn giản, nên C++ và Python cho đúng cùng một bộ test với cùng tham số, tiện cho việc so sánh hai bài với nhau:

Xem code C++ / Python: sinh test ngẫu nhiên từ tham số seed, n, lo, hi (bấm để mở)
#include <bits/stdc++.h>
using namespace std;

long long seed;
long long rnd(){                                   // số giả ngẫu nhiên tự viết, giống hệt bản Python
    seed = (seed * 1103515245 + 12345) % 2147483648LL;
    return seed;
}

int main(){
    long long n, lo, hi;
    cin >> seed >> n >> lo >> hi;                  // chạy: gen < thamso.txt > BAI1.INP
    cout << n << "\n";
    for(int i = 0; i < n; i++)
        cout << lo + rnd() % (hi - lo + 1) << (i + 1 < n ? " " : "\n");
    return 0;
}
seed, n, lo, hi = map(int, input().split())        # chạy: python gen.py < thamso.txt > BAI1.INP

def rnd():                                         # số giả ngẫu nhiên tự viết, giống hệt bản C++
    global seed
    seed = (seed * 1103515245 + 12345) % 2147483648
    return seed

print(n)
print(' '.join(str(lo + rnd() % (hi - lo + 1)) for _ in range(n)))

Khi đã có bộ sinh test, bước tiếp theo là viết thêm một bài “trâu” (vét cạn, chắc chắn đúng nhưng chậm) và cho hai bài chạy trên hàng trăm test ngẫu nhiên rồi so kết quả: đó là kỹ thuật chấm trâu, thầy đã hướng dẫn chi tiết kèm script tự động ở bài 22. Với chuyên đề này, em chỉ cần thuộc lòng: khung bài nộp chuẩn, năm dạng input, dấu phân cách in trước và bộ ba nhập xuất nhanh.

Lưu khung bài làm sẵn
Trong phòng thi, việc đầu tiên sau khi đọc đề là tạo đủ các file nguồn với khung chuẩn ở phần 1, đổi tên file trong freopen cho từng bài, biên dịch thử. Làm xong bước này khi đầu óc còn tỉnh táo, em sẽ không bao giờ phải lo chuyện tên file vào phút cuối. Khi luyện đề ở nhà hay thi thử trên CLUE OJ, hãy tập luôn thói quen này.
Luyện tập trên web
Các bài dưới đây trên blog đều có lời giải chi tiết bằng C++ và Python, xếp từ dễ tới khó. Em hãy tự làm trước rồi mới xem lời giải nhé.Xem toàn bộ bài cùng chủ đề: nhãn Thi HSG Tin học.

Bài tậpLuyện đọc ghi đúng định dạng

Tám bài dưới đây thuật toán đều nhẹ nhàng, cái khó nằm ở đọc đúng và in đúng. Em hãy làm như trong phòng thi: dùng khung bài nộp chuẩn, tạo file .INP từ ví dụ, tự thêm test biên, và kiểm tra output từng ký tự.

Bài 1Tổng tới hết fileDễ

File dữ liệu gồm các số nguyên $x$ ($|x| \le 10^9$) viết trên một hoặc nhiều dòng, giữa các số có thể có nhiều dấu cách, có thể có dòng trống. Số lượng số không cho trước (ít nhất 1, nhiều nhất $10^6$). In ra số lượng số trên dòng thứ nhất và tổng của chúng trên dòng thứ hai.

Input
3 5
-2

10   4
Output
5
20

📝 Nộp bài, chấm tự động trên ClueOJ ↗

💡 Gợi ý
Dạng 3: while(cin >> x) tự bỏ qua mọi dấu cách, dòng trống. Tổng lớn nhất cỡ $10^{15}$ nên dùng long long. Python: sys.stdin.buffer.read().split(). Mẫu có 5 số 3, 5, −2, 10, 4 với tổng 20.
Xem code tham khảo (C++ / Python)
#include <bits/stdc++.h>
using namespace std;

int main(){
    ios::sync_with_stdio(false); cin.tie(nullptr);
    long long x, s = 0;
    int cnt = 0;
    while(cin >> x){
        cnt++;
        s += x;
    }
    cout << cnt << "\n" << s << "\n";
    return 0;
}
import sys

a = sys.stdin.buffer.read().split()
print(len(a))
print(sum(map(int, a)))
Bài 2Dừng ở lính canhDễ

Mỗi dòng của file dữ liệu ghi hai số nguyên dương $a$, $b$ ($a, b \le 10^{18}$). Dữ liệu kết thúc bởi dòng 0 0 (không xử lý dòng này); sau dòng 0 0 có thể còn dữ liệu khác, em phải bỏ qua. Với mỗi cặp, in ước chung lớn nhất của $a$ và $b$ trên một dòng. Có không quá $10^5$ cặp.

Input
12 18
7 5
100 75
0 0
9 3
Output
6
1
25

📝 Nộp bài, chấm tự động trên ClueOJ ↗

💡 Gợi ý
Đọc từng cặp trong while, gặp 0 0 thì break ngay. Dòng 9 3 nằm sau lính canh nên không được in. Số tới $10^{18}$ nên dùng long long; C++17 có sẵn gcd trong <numeric>, Python có math.gcd.
Xem code tham khảo (C++ / Python)
#include <bits/stdc++.h>
using namespace std;

int main(){
    ios::sync_with_stdio(false); cin.tie(nullptr);
    long long a, b;
    while(cin >> a >> b){
        if(a == 0 && b == 0) break;          // lính canh: dừng, không xử lý
        cout << gcd(a, b) << "\n";
    }
    return 0;
}
import sys
from math import gcd

data = sys.stdin.buffer.read().split()
out = []
for i in range(0, len(data) - 1, 2):
    a, b = int(data[i]), int(data[i + 1])
    if a == 0 and b == 0:                    # lính canh: dừng, không xử lý
        break
    out.append(gcd(a, b))
print('\n'.join(map(str, out)))
Bài 3Case #tDễ

Có $T$ bộ test ($T \le 10^4$). Mỗi bộ gồm dòng đầu là $n$ ($1 \le n$, tổng $n$ của mọi bộ không quá $2 \cdot 10^5$), dòng sau là $n$ số nguyên $|a_i| \le 10^9$. Với bộ thứ $t$, in một dòng đúng dạng Case #t: d, trong đó $d$ là hiệu giữa số lớn nhất và số nhỏ nhất của bộ đó.

Input
2
3
4 9 1
1
-5
Output
Case #1: 8
Case #2: 0

📝 Nộp bài, chấm tự động trên ClueOJ ↗

💡 Gợi ý
Dạng 2 kèm định dạng có chữ. Khai báo mx, mn bên trong vòng lặp test để mỗi bộ bắt đầu lại từ đầu. Hiệu có thể tới $2 \cdot 10^9$, vượt int. In đúng từng ký tự: Case viết hoa chữ C, dấu # dính liền số, một dấu cách sau dấu hai chấm.
Xem code tham khảo (C++ / Python)
#include <bits/stdc++.h>
using namespace std;

int main(){
    ios::sync_with_stdio(false); cin.tie(nullptr);
    int T;
    cin >> T;
    for(int t = 1; t <= T; t++){
        int n;
        cin >> n;
        long long mx = LLONG_MIN, mn = LLONG_MAX;     // khởi tạo lại cho từng bộ
        for(int i = 0; i < n; i++){
            long long x;
            cin >> x;
            mx = max(mx, x);
            mn = min(mn, x);
        }
        cout << "Case #" << t << ": " << mx - mn << "\n";
    }
    return 0;
}
import sys

data = sys.stdin.buffer.read().split()
T = int(data[0])
p = 1
out = []
for t in range(1, T + 1):
    n = int(data[p]); p += 1
    a = list(map(int, data[p:p + n])); p += n      # lấy đúng n số của bộ này
    out.append(f"Case #{t}: {max(a) - min(a)}")
print('\n'.join(out))
Bài 4Dòng dài nhấtDễ

File dữ liệu là một đoạn văn bản gồm không quá $1000$ dòng, mỗi dòng không quá $1000$ ký tự (chữ cái, chữ số, dấu cách, dấu câu; có thể có dòng trống). File có thể được soạn trên Windows (xuống dòng là \r\n). Dấu xuống dòng ở cuối dòng cuối cùng (nếu có) không tạo thêm dòng mới. In ra: số dòng; độ dài của dòng dài nhất (không tính ký tự \r, \n); và chính dòng đó (nếu nhiều dòng cùng dài nhất thì in dòng xuất hiện đầu tiên).

Input
Hoc lap trinh
khong kho

chi can kien tri
Output
4
16
chi can kien tri

📝 Nộp bài, chấm tự động trên ClueOJ ↗

💡 Gợi ý
Dạng 4: while(getline(cin, s)), xoá '\r' cuối mỗi dòng rồi so độ dài. Dùng dấu > (không phải >=) để giữ dòng đầu tiên khi bằng nhau. Python đọc nhị phân rồi split(b'\n'), bỏ phần tử rỗng cuối cùng nếu file kết thúc bằng xuống dòng. Mẫu có 4 dòng, dòng thứ ba trống, dòng cuối dài 16 ký tự.
Xem code tham khảo (C++ / Python)
#include <bits/stdc++.h>
using namespace std;

int main(){
    string s, best;
    int cnt = 0;
    bool has = false;
    while(getline(cin, s)){
        if(!s.empty() && s.back() == '\r') s.pop_back();
        cnt++;
        if(!has || s.size() > best.size()){ best = s; has = true; }
    }
    cout << cnt << "\n" << best.size() << "\n" << best << "\n";
    return 0;
}
import sys

lines = sys.stdin.buffer.read().split(b'\n')
if lines and lines[-1] == b'':
    lines.pop()                                   # '\n' cuối file không tạo dòng mới
lines = [x.rstrip(b'\r').decode() for x in lines]
best = lines[0]
for s in lines:
    if len(s) > len(best):
        best = s
print(len(lines))
print(len(best))
print(best)
Bài 5Bảng điểmTrung bình

Dòng đầu là $n$ ($1 \le n \le 10^4$). Mỗi dòng trong $n$ dòng tiếp theo ghi họ tên một học sinh (một hoặc nhiều từ chỉ gồm chữ cái tiếng Anh, giữa các từ có thể có nhiều dấu cách) và cuối dòng là điểm của bạn đó (số thực từ 0 tới 10, tối đa 2 chữ số sau dấu chấm). In ra họ tên của bạn điểm cao nhất (nếu bằng nhau lấy bạn đứng trước), các từ cách nhau đúng một dấu cách; dòng thứ hai in điểm trung bình của cả lớp với đúng 2 chữ số sau dấu chấm.

Input
3
Nguyen  Van An 8.5
Tran Binh 9.25
Le   Thi Chau  7
Output
Tran Binh
8.25

📝 Nộp bài, chấm tự động trên ClueOJ ↗

💡 Gợi ý
Đọc cả dòng (nhớ bỏ phần còn lại của dòng chứa $n$), tách thành các từ: từ cuối là điểm, các từ còn lại ghép bằng một dấu cách là họ tên. Trong C++ dùng stringstream lấy từng từ vào vector, Python dùng line.split() (tự xử lý nhiều dấu cách và '\r'). Trung bình $(8.5 + 9.25 + 7) / 3 = 8.25$, in bằng fixed + setprecision(2).
Xem code tham khảo (C++ / Python)
#include <bits/stdc++.h>
using namespace std;

int main(){
    int n;
    cin >> n;
    cin.ignore(numeric_limits<streamsize>::max(), '\n');
    string line, bestName;
    double best = -1, sum = 0;
    for(int i = 0; i < n; i++){
        getline(cin, line);
        stringstream ss(line);
        vector<string> w;
        string t;
        while(ss >> t) w.push_back(t);          // tách từ, bỏ dấu cách thừa và '\r'
        double d = stod(w.back());               // từ cuối là điểm
        string name = w[0];
        for(int k = 1; k + 1 < (int)w.size(); k++) name += " " + w[k];
        sum += d;
        if(d > best){ best = d; bestName = name; }
    }
    cout << bestName << "\n";
    cout << fixed << setprecision(2) << sum / n << "\n";
    return 0;
}
import sys

lines = sys.stdin.buffer.read().decode().split('\n')
n = int(lines[0])
best, best_name, total = -1.0, '', 0.0
for i in range(1, n + 1):
    w = lines[i].split()                 # tách từ, bỏ dấu cách thừa và '\r'
    d = float(w[-1])                     # từ cuối là điểm
    total += d
    if d > best:
        best, best_name = d, ' '.join(w[:-1])
print(best_name)
print(f"{total / n:.2f}")
Bài 6Chuyển vị ma trậnTrung bình

Dòng đầu gồm $n$, $m$ ($1 \le n, m \le 500$). Tiếp theo là $n \cdot m$ số nguyên của ma trận theo thứ tự từng hàng, nhưng người soạn đề xuống dòng tuỳ ý (một hàng có thể bị cắt làm nhiều dòng, nhiều hàng có thể nằm chung một dòng). In ra ma trận chuyển vị: $m$ dòng, mỗi dòng $n$ số, các số cách nhau đúng một dấu cách, không có dấu cách thừa cuối dòng.

Input
2 3
1 2
3 4 5
6
Output
1 4
2 5
3 6

📝 Nộp bài, chấm tự động trên ClueOJ ↗

💡 Gợi ý
Vì cin >> không quan tâm xuống dòng, cứ đọc lần lượt $n \cdot m$ số vào a[i][j] là đúng thứ tự. Phần định dạng: hàng $j$ của ma trận mới là cột $j$ của ma trận cũ; in số cuối hàng thì kèm '\n', các số khác kèm dấu cách. Mẫu: ma trận cũ là [[1 2 3], [4 5 6]].
Xem code tham khảo (C++ / Python)
#include <bits/stdc++.h>
using namespace std;

int a[505][505];

int main(){
    ios::sync_with_stdio(false); cin.tie(nullptr);
    int n, m;
    cin >> n >> m;
    for(int i = 0; i < n; i++)
        for(int j = 0; j < m; j++) cin >> a[i][j];
    for(int j = 0; j < m; j++)
        for(int i = 0; i < n; i++)
            cout << a[i][j] << (i + 1 < n ? ' ' : '\n');
    return 0;
}
import sys

data = sys.stdin.buffer.read().split()
n, m = int(data[0]), int(data[1])
v = data[2:2 + n * m]
out = []
for j in range(m):
    out.append(b' '.join(v[i * m + j] for i in range(n)).decode())
print('\n'.join(out))
Bài 7Truy vấn tổng đoạn, dữ liệu lớnTrung bình

Cho dãy $n$ số nguyên $a_1, \ldots, a_n$ ($n \le 5 \cdot 10^5$, $|a_i| \le 10^9$) và $q$ truy vấn ($q \le 5 \cdot 10^5$), mỗi truy vấn gồm $l$, $r$ ($1 \le l \le r \le n$). Với mỗi truy vấn, in tổng $a_l + \ldots + a_r$ trên một dòng. Giới hạn thời gian 1 giây. Dòng đầu là $n$, $q$; dòng hai là dãy số; $q$ dòng sau là các truy vấn.

Input
5 3
1 -2 3 4 5
1 5
2 3
4 4
Output
11
1
4

📝 Nộp bài, chấm tự động trên ClueOJ ↗

💡 Gợi ý
Thuật toán là mảng cộng dồn ở bài 13: $S_r - S_{l-1}$. Cái khó là tổng cộng hơn một triệu số vào và nửa triệu dòng ra: C++ bắt buộc có sync_with_stdio(false), cin.tie(nullptr), '\n'; Python đọc một lần bằng sys.stdin.buffer và in một lần bằng join. Dùng endl hoặc print từng dòng là quá thời gian.
Xem code tham khảo (C++ / Python)
#include <bits/stdc++.h>
using namespace std;

long long S[500005];

int main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int n, q;
    cin >> n >> q;
    for(int i = 1; i <= n; i++){
        long long x;
        cin >> x;
        S[i] = S[i - 1] + x;
    }
    while(q--){
        int l, r;
        cin >> l >> r;
        cout << S[r] - S[l - 1] << '\n';
    }
    return 0;
}
import sys

def main():
    data = sys.stdin.buffer.read().split()
    n, q = int(data[0]), int(data[1])
    S = [0] * (n + 1)
    for i in range(n):
        S[i + 1] = S[i] + int(data[2 + i])
    p = 2 + n
    out = []
    for _ in range(q):
        l, r = int(data[p]), int(data[p + 1]); p += 2
        out.append(S[r] - S[l - 1])
    sys.stdout.write('\n'.join(map(str, out)) + '\n')
main()
Bài 8Tách số khỏi văn bảnKhá

File dữ liệu là một đoạn văn bản tối đa $10^6$ ký tự, gồm nhiều dòng (đọc tới hết file). Một số là một dãy chữ số liên tiếp dài nhất có thể; nếu ngay trước dãy đó là dấu - thì đó là số âm. Ví dụ a-12b chứa số −12; 3-5 chứa hai số 3 và −5; --7 chứa −7; dấu - ở cuối dòng không ảnh hưởng tới số ở đầu dòng sau. Mỗi số có không quá 9 chữ số. In ra số lượng số và tổng của chúng, mỗi giá trị một dòng.

Input
Lop 9A co 35 hoc sinh, nhiet do -2 do.
Nam 2026: diem 8-9
Output
6
2067

📝 Nộp bài, chấm tự động trên ClueOJ ↗

💡 Gợi ý
Đọc toàn bộ file vào một xâu (C++: string s((istreambuf_iterator<char>(cin)), istreambuf_iterator<char>());) để không phải lo xuống dòng, rồi duyệt từng ký tự: gặp chữ số thì gom hết dãy chữ số, xem ký tự ngay trước dãy có phải - không. Python dùng biểu thức chính quy trên dữ liệu nhị phân: re.findall(rb'-?[0-9]+', data). Mẫu có các số 9, 35, −2, 2026, 8, −9, tổng 2067. Tổng có thể tới cỡ $5 \cdot 10^{14}$ nên dùng long long.
Xem code tham khảo (C++ / Python)
#include <bits/stdc++.h>
using namespace std;

int main(){
    string s((istreambuf_iterator<char>(cin)), istreambuf_iterator<char>());   // đọc toàn bộ file
    long long sum = 0;
    int cnt = 0, n = s.size(), i = 0;
    while(i < n){
        if(isdigit((unsigned char)s[i])){
            bool neg = (i > 0 && s[i - 1] == '-');
            long long v = 0;
            while(i < n && isdigit((unsigned char)s[i])){ v = v * 10 + (s[i] - '0'); i++; }
            sum += neg ? -v : v;
            cnt++;
        } else i++;
    }
    cout << cnt << "\n" << sum << "\n";
    return 0;
}
import sys, re

data = sys.stdin.buffer.read()                       # đọc toàn bộ file dạng nhị phân
nums = [int(x) for x in re.findall(rb'-?[0-9]+', data)]
print(len(nums))
print(sum(nums))
Rất vui khi anh em nào đã đọc đến những dòng cuối cùng này. Anh em đọc chắc cũng mỏi mắt luôn chứ 😅 vậy anh em nghĩ người viết ra hết đống ở trên còn thế nào nữa 🤪. Vậy nên đừng tiếc gì cho mình một lời bình luận hoặc chia sẻ bài viết để động viên mình nè. Chúc anh em học tốt và gặt hái nhiều thành công 😇
Bản quyền thuộc về Thầy Nguyễn Việt Nam Sơn. Bài viết, lời giải, hình minh hoạ và mã nguồn trên trang là công sức biên soạn của thầy qua nhiều năm giảng dạy. Vui lòng không sao chép, đăng lại hay sử dụng dưới mọi hình thức khi chưa được sự đồng ý của tác giả. Nếu thấy hữu ích, hãy chia sẻ bằng đường link bài gốc nhé. Cảm ơn bạn!
Tự học thuật toán: khó ở đâu?
Đọc xong một bài giải là bước đầu. Phần lớn học sinh tự học sẽ gặp những trở ngại sau:
  • Không biết học gì trước, học gì sau, học lan man nên kiến thức bị hổng.
  • Bí bài không ai gỡ, ngồi cả buổi vẫn không ra rồi bỏ cuộc.
  • Code chạy được nhưng không đủ điểm, không biết sai ở đâu, chậm ở đâu.
  • Không có đề thi thử và người chấm, nên không biết mình đang ở mức nào.
  • Dễ nản giữa chừng khi không có ai theo sát.
Học cùng thầy Nam Sơn, các trở ngại đó được gỡ bỏ:
  • Lộ trình 111 buổi video sắp sẵn từ dòng code đầu tiên đến đề HSG thật của các tỉnh.
  • Thầy trực tiếp gỡ bí, chữa bài cho từng em qua nhóm Zalo riêng.
  • Hệ thống chấm bài tự động 24/7 và thi thử HSG hằng tuần để biết chính xác mình đang ở đâu.
  • Đóng học phí một lần, thầy kèm lâu dài: từ thi chuyên Tin đến HSG quốc gia và cả khi lên đại học.

Nhận xét