Những Lưu Ý Quan Trọng Khi Lập Trình Thi Đấu

Tóm tắt

  • Luôn kiểm tra kích thước mảng sau khi viết xong bài. Nên khai báo lớn hơn 2-4 lần so với giới hạn đề bài. Đặc biệt chú ý khi có nhiều biến như N, M, K...
  • Phải đọc kỹ đề bài. Dữ liệu kiểm thử có thể có nhiều dạng khác nhau. Nếu đề không nói rõ không có cạnh song song hoặc tự vòng, hãy tự xử lý.
  • Xác định rõ dữ liệu đầu vào và yêu cầu đầu ra. Ví dụ, khi đọc dữ liệu, hãy tự hỏi: x có phải là chỉ số, màu sắc, hay giá trị của đỉnh i không? Thông tin về cạnh (x, y, z) có thể không phải là cạnh x->y có trọng số z, mà có thể là y->z có trọng số x, hoặc thậm chí là y->x có trọng số z.
  • Chú ý có phải là nhiều bộ test hay không, và tên file có chính xác không.
  • Chú ý không bị tràn kiểu int. Trong các kỳ thi, có thể định nghĩa int là long long. Nếu tràn long long, hãy thử dùng unsigned long long hoặc chuyển sang long double rồi chuyển lại.
  • Khi viết code, đầu óc phải tỉnh táo. Tránh các lỗi cơ bản như gõ nhầm N thành M, tmp1 thành tmp2, ls thành l, j thành i...
  • Nếu cảm thấy mệt mỏi hoặc nôn nóng khi gỡ lỗi, hãy ra ngoài đi dạo rồi quay lại. Việc này giúp đầu óc sảng khoái hơn.
  • Trong các buổi tập huấn, hãy chọn một nơi tốt (nếu có thể), nếu không sẽ gặp vấn đề như không nhìn rõ bảng, không tập trung được.
  • Trong cuộc thi, tuyệt đối không được hoảng loạn.
  • Khi suy nghĩ giải pháp, đừng vội tối ưu hóa hằng số. Ưu tiên tối ưu hóa độ phức tạp thuật toán.
  • Đừng quên khởi tạo các biến! (Đặc biệt với nhiều bộ test)
  • Nếu muốn sao lưu code, đừng sao chép trực tiếp file .cpp. Hãy sao chép code, sau đó tạo một file .cpp mới.
  • Nếu không biết cách giải chính thức, hãy suy nghĩ về các phương pháp lấy điểm phần, và thử mở rộng chúng. Thường thì các bài toán hay sẽ có manh mối trong các điểm phần.
  • Đối với các bài toán toán học "khó nhằn" (đặc biệt n <= 1e9), việc "đánh bảng" (brute force với các giá trị nhỏ) là rất quan trọng. Bảng kết quả có thể giúp ta tìm ra đáp án mà không cần đến các công thức phức tạp.
  • Lưu đồ và quy hoạch động thường là các hướng giải khó nghĩ. Khi không có ý tưởng, hãy thử các hướng này.
  • Khai báo mảng thật lớn! Luôn kiểm tra xem kích thước mảng đã đủ lớn chưa.
  • Luôn đối chiếu kết quả với một giải pháp khác (đối chiếu). Dù không biết cách đối chiếu, hãy tự tạo nhiều bộ dữ liệu kiểm thử.
  • Các bước kiểm tra trong 20 phút cuối kỳ thi: kiểm tra kích thước mảng, kiểm tra mẫu có chạy được không, kiểm tra các biến có bị nhầm lẫn không, đọc lại đề một lần nữa, và kiểm tra tên file.
  • Hãy thử chạy code trên môi trường Linux. Nhiều lỗi biên dịch (CE) chỉ xuất hiện ở đây mà không có ở Windows.
  • Tự tạo dữ liệu kiểm thử dựa trên đề bài. Đừng tự tin vào kết luận của mình.
  • Đừng chỉ nghĩ đến các giải pháp lấy điểm phần. Hãy suy nghĩ về giải pháp chính thức. Đôi khi giải pháp chính thức còn dễ viết hơn.
  • Trong cuộc thi, nếu biết cách giải chính thức, hãy viết ngay lập tức. Nếu không, hãy viết giải pháp bạo lực.
  • Đừng dành quá nửa thời gian cuộc thi cho một bài toán (đặc biệt là T1).
  • Các phép cắt tỉa có tác dụng tối ưu hóa nên được thêm vào, ngay cả khi chúng không hiệu quả trong trường hợp xấu nhất.
  • Trong CodeForces, khi viết hàm băm (Hash), nên dùng băm kép và thêm yếu tố ngẫu nhiên hóa để tránh bị "hack".
  • Tự tạo dữ liệu kiểm thử càng nhiều càng tốt. Việc này giúp phát hiện lỗi hiệu quả hơn là xem code.
  • Sau khi giải xong các bài đơn giản, hãy suy nghĩ về các trường hợp biên.
  • Khi có một ý tưởng nhỏ, hãy thử nó. Hãy thử tất cả các hướng tiếp cận có thể (DFS, BFS, Iterative Deepening...).
  • Đối với các bài toán yêu cầu trả lời, hãy quan sát đặc điểm của dữ liệu trước khi viết code bạo lực.
  • Đối chiếu không nên chỉ dùng `int n = 100, m = 100`. Hãy thử các giá trị n, m khác nhau, hoặc dùng `rand()`.
  • Đối chiếu các bài toán về cây, hãy tạo các dữ liệu đặc biệt như "cây hoa cúc" (nhiều cạnh nối vào một đỉnh) và "cây dây" (độ sâu lớn).
  • Trước khi chạy đối chiếu, hãy đảm bảo code đã được biên dịch.
  • Hãy suy nghĩ về giải pháp trong khoảng 1.5 giờ đầu tiên của kỳ thi 4.5 giờ để ổn định và giảm bớt áp lực.
  • Hãy có niềm tin! Đôi khi việc khai báo mảng lớn hoặc viết các giải pháp "lừa" có thể giúp bạn qua được bài.
  • Khi code gặp lỗi lạ, hãy suy nghĩ kỹ nguyên nhân, đừng tự欺骗 mình.
  • Đọc lại code một lần nữa sau khi đã chạy qua mẫu.
  • Quan sát phạm vi dữ liệu. Nếu cảm thấy bài quá khó, hãy đọc lại đề để kiểm tra xem có bỏ sót điều kiện nào không.

Các Lỗi Thường Gặp Cụ Thể

Trên POJ (CE - Lỗi Biên Dịch)

  • Thay thế thư viện万能头 bằng các thư viện cụ thể.
  • Kiểm tra xem có truyền một long long vào hàm yêu cầu int không.
  • Thay đổi ngôn ngữ trên POJ thành G++.
  • Nếu DevC++ cũng báo lỗi `invalid types 'int[int]' for array subscript`, có thể bạn đã dùng một biến int như một mảng int[]. Hãy kiểm tra các tên biến trùng lặp.

Trên POJ (RE - Lỗi Chạy Thời Gian)

  • Thay đổi ngôn ngữ trên POJ thành C++ và kiểm tra lại kiểu dữ liệu.
  • Khai báo mảng lớn hơn 10 lần.
  • Kiểm tra các trường hợp `sqrt(-xxx)`, `xxx/0`, `h[-5]`, `h[1047483647]`.
  • Đối chiếu với code chuẩn hoặc nhúng code của bạn vào code chuẩn.

Trên OJ (MLE - Lỗi Dùng Quá Nhiều Bộ Nhớ)

  • Điều chỉnh kích thước mảng (tăng hoặc giảm).
  • Kiểm tra xem có rơi vào vòng lặp vô hạn không.
  • Tối ưu hóa thuật toán.

Trên OJ hoặc thi (WA - Lỗi Sai Kết Quả)

  • Dữ liệu nhỏ: tự chơi tay.
  • Dữ liệu lớn: thử khai báo mảng lớn hơn.
  • Định nghĩa int là long long.
  • Trên UVA, lỗi định dạng (ví dụ: có dòng trống thừa) rất phổ biến.

Lỗi Chỉ Xuất Hiện Trên Linux

  • `typedef long long uint;`
  • `printf("%.3llf ", ans);`
  • `define int long long` sau đó `typedef unsigned int uint;`

Lưu Ý Khác

  • Nếu một biến thay đổi giá trị một cách lạ thường (đặc biệt với dữ liệu lớn), hãy thử khai báo mảng lớn hơn. Nếu lỗi biến mất, có nghĩa là mảng của bạn không đủ lớn.
  • Khi viết cây phân đoạn động, trong hàm `pushdown`, hãy kiểm tra xem node có con trái/phải không để tránh mất dấu.
  • Khi so sánh chuỗi, hãy dùng `strcmp(a, b)`.
  • Các biến đếm như `cnt`, `cnt1`, `cnt2`... cần được phân biệt rõ ràng.
  • Phân biệt toán tử dịch bit trái và phải.
  • Xem kỹ yêu cầu đầu ra, đừng làm tròn hoặc lấy phần nguyên một cách mù quáng.
  • Nếu không tìm ra lỗi, hãy thử viết lại code.
  • Trong DFS, tránh duyệt thừa. Đặc biệt khi liệt kê các hoán vị, không chọn (5, 6) rồi lại chọn (6, 5).
  • Luôn kiểm tra tràn int/long long. Nên dùng long long thay cho int.
  • Khai báo mảng lớn hơn một chút để tránh các lỗi kỳ lạ.
  • Phân biệt biến cục bộ và biến toàn cục.
  • Trong DFS, hãy khôi phục trạng thái.
  • Khi sửa lỗi, hãy sửa cho đủ.
  • Chú ý định dạng đầu ra.

Về Quy Hoạch Động (DP)

  • Chú ý thứ tự cập nhật trạng thái. Đôi khi cần cập nhật ngược lại.
  • Đã bao phủ tất cả trạng thái chưa? (Đúng đắn)
  • Khi tính trạng thái sau, trạng thái trước đã được xác định chưa? (Vô hậu)
  • Đã sửa đổi trạng thái đã được xác định chưa?

Về Cây Phân Cành (Tree Chain Decomposition)

  • Để tìm LCA, hãy dùng:
    if (depth[top[x]] < depth[top[y]]) swap(x, y);
    Thay vì:
    if (depth[x] < depth[y]) swap(x, y);
  • Trong hàm `get_lca`, hãy dùng `while(top[x] != top[y])` thay vì `if(top[x] != top[y])`.

Về Nhập Liệu

  • Khi dùng hàm đọc nhanh (fast read), hãy cẩn thận với ký tự tiếp theo.
  • Cách đọc một ký tự đúng:
    char ch[5];
    scanf("%s", ch);
    // Sử dụng ch[0]

Về Các Hàm Thư Viện

  • Không nên lạm dụng `memset(0x3f)`. Khi cộng hoặc nhân hai số đã được `memset(0x3f)`, kết quả có thể tràn. Hãy kiểm tra nếu cả hai đều là 0x3f thì gán lại 0x3f.
  • Trong cây, để giải quyết bài toán đường đi chung của ba điểm, hãy lấy LCA của hai điểm có dfn cách xa nhất.
  • Trong hàm `dinic`, hãy thêm tối ưu hóa đường đi và khởi tạo các biến.
  • Trong các trường hợp đặc biệt, hãy dùng `continue` hoặc `return`.
  • Trong SPFA, hãy khai báo queue có kích thước nm.
  • Trong các thuật toán cần mảng depth, hãy đặt depth của gốc là 1.
  • Trong单调队列/单调栈, nếu phần tử hiện tại và phần tử cuối cùng equally optimal, hãy pop phần tử cuối cùng.
  • Khi chèn vào cơ sở tuyến tính, hãy kiểm tra bit j có phải là 1 không.
  • Trong các container STL như set, `end()` không chứa dữ liệu, truy cập sẽ gây RE, nhưng `begin()` chứa phần tử đầu tiên.
  • Trong DP, khi có điều kiện "bắt buộc chọn...", hãy xem xét có bị bỏ sót trường hợp nào không.
  • Khi gặp các bài toán về "xâu con", hãy nghĩ đến SA, SAM, hoặc AC automation.
  • Chú ý sự khác biệt giữa `cur`, `to`, `fa`.
  • Phân biệt `dfn` và `d[cur]`.
  • Đừng quên sắp xếp! Nếu có hai khóa chính, hãy sắp xếp theo hai khóa.
  • Nếu stack hệ thống không đủ, hãy mở stack thủ công:
    -Wl,--stack=67108864 (Windows)
    ulimit -s 131072 (Linux)
  • Đối với các bài toán "bất hòa" trên lưới, hãy nghĩ đến DP hoặc luồng mạng (cặp ghép hai phía).
  • Trong cây phân đoạn động, hãy phân biệt `ls` và `rs` với khoảng `[L, mid]` và `[mid+1, R]`.
  • Khi xây dựng bảng ST, hãy lặp j trước, rồi lặp i. Đảm bảo `i + (1 << (j - 1))` không vượt quá n.
  • Đối với các bài toán về "thêm số vào đoạn, truy vấn k-th lớn nhất", hãy nghĩ đến cây phân đoạn có持久化.
  • Trong hàm so sánh của `sort`, hãy đặt tên khác `bcmp` (tránh lỗi trên một số OJ).
  • Đôi khi các bài toán toán học có thể giải bằng DP (ví dụ: số sai vị trí).
  • Chú ý các phép tính cơ bản: 4000 - 1 = 3999.
  • Khi phân tích thừa số nguyên tố, hãy kiểm tra trường hợp x != 1.
  • Để loại bỏ cạnh song song, hãy dùng vector và `sort` + `unique`.
  • Trong thuật toán Tarjan, hãy đánh dấu `instk` khi đẩy vào stack và xóa khi pop ra.
  • Đừng "cố" đặt giá trị vô cực (inf) sát với giá trị lớn nhất có thể.
  • Sử dụng `typedef` một cách cẩn thận.
  • Có số âm, đừng dùng unsigned.
  • Tổng tiền tố: `prefix[i] = prefix[i-1] + f(i)`, không phải `prefix[i] = prefix[i] + f(i)`.
  • Phân biệt việc đặt một biểu thức trong hoặc ngoài vòng lặp.
  • Phân biệt `<=` và `<`.
  • Trong 01-BFS, hãy viết theo kiểu SPFA và không dùng mảng `vis`.

Các Lỗi Nhỏ

  • Xóa hết các câu lệnh gỡ lỗi (`debug`).
  • Chú ý định dạng đầu ra.
  • Gọi hàm khởi tạo `init` nếu có.
  • Xóa sạch dữ liệu cho các bộ test tiếp theo.
  • Trong các bài toán lấy模, nếu kết quả âm, hãy cộng P trước khi in ra.
  • Khởi tạo `jie[0] = jieni[0] = 1`.
  • Trong tìm kiếm có nhớ, hãy kiểm tra `if (mp.count(...))` và `return mp[] = res;`.
  • Trong các phép toán lấy模, chú ý kết quả âm.
  • Phân biệt chỉ số và giá trị, đặc biệt trong các mảng hoán vị và mảng vị trí.
  • Vector rỗng cũng chiếm không gian bộ nhớ. Hạn chế khai báo quá nhiều vector.
  • `vector.clear()` không giải phóng bộ nhớ ngay. Để giải phóng, hãy dùng `vector().swap(vec)`.

Thẻ: lập trình thi đấu thuật toán Gỡ lỗi Cấu trúc dữ liệu C++

Đăng vào ngày 26 tháng 5 lúc 16:28