9

Tạp chí VNOI Xuân Bính Ngọ - Tổng hợp những sai lầm có thể mắc phải trong kỳ thi VOI 2026

đã đăng vào 9, Tháng 2, 2026, 8:00

Tổng hợp những sai lầm có thể mắc phải trong kỳ thi VOI 2026

Tác giả: Hoàng Dương - THPT chuyên Nguyễn Huệ, Hà Nội.

Giới thiệu

Kỳ thi chọn Học sinh giỏi Quốc gia môn Tin học (VOI) năm học 2025-2026 đã chính thức khép lại, nhưng dư âm của nó chắc hẳn vẫn còn len lỏi trong tâm trí của mỗi sĩ tử. Đó là nơi xuất hiện những cung bậc cảm xúc trái ngược: có bạn vỡ òa khi điểm số thực tế vượt xa dự tính, nhưng cũng không ít bạn phải nếm trải cảm giác tiếc nuối – cái cảm giác "lẽ ra mình phải làm tốt hơn thế" khi nhận ra những sai sót không đáng có. Bài viết này không tập trung vào lời giải (các bạn có thể tham khảo trên VNOI), mà sẽ phân tích những sai lầm phổ biến trong tư duy và cài đặt mà nhiều thí sinh đã mắc phải.

Đánh giá một cách công tâm, đề thi năm nay không chỉ khó về thuật toán mà còn chứa nhiều bẫy tinh vi, cả trong tư duy xây dựng lời giải và cài đặt. Các lỗi sai đó cho thấy, để có được kết quả cao, các thí sinh không chỉ cần kiến thức, mà còn cần một cái đầu lạnh và sự cẩn trọng tuyệt đối trước áp lực của thể thức thi Offline, nơi các thí sinh chỉ được nộp 1 lần.

Hy vọng rằng bài viết này sẽ mang lại cho các bạn một chút tiếng cười giải tỏa (dù có thể là cười ra nước mắt), và quan trọng hơn cả, là những bài học đáng giá cho kỳ thi VOI hay các kỳ thi Offline khác trong tương lai. Bây giờ, hãy cùng điểm qua những pha mất điểm đáng tiếc, được mình tổng hợp từ trải nghiệm thực tế và từ những cuộc thảo luận trên Discord VNOI nhé!

Bài 1 - Dãy đèn (LIGHT)

Bài toán yêu cầu tìm số thao tác ít nhất để đưa dãy đèn về trạng thái tắt thông qua các phép xoay xuôi và ngược. Dù ~N = 666~ trông có vẻ nhẹ nhàng cho bài đầu tiên, nhưng đây lại là một bài toán BFS trên đồ thị trạng thái, với những cái bẫy về bộ nhớ và toán học không dễ để nhận ra.

1. Lỗi BFS ngược

Để giải quyết bài toán, chiến thuật phổ biến là gom các truy vấn có cùng độ dài để BFS ngược từ trạng thái đích ~(cnt_0, cnt_1, cnt_2) = (len, 0, 0)~, với ~len = r - l + 1~, về các trạng thái cần tìm.

Cũng từ hướng đi này, đã có trường hợp quên đảo ngược chiều xuôi và ngược (hay đơn giản là swap ~X~ và ~Y~). Khi đi ngược từ đích về nguồn, nếu chiều thuận là xoay ~X~ xuôi, ~Y~ ngược thì chiều nghịch phải tương ứng đảo lại.

Tai hại hơn, stress test trong bài này thường được thực hiện với subtask ~X = Y = 1~ (Subtask 1 & 2). Đây là trường hợp đặc biệt, nên dù quên swap thì code vẫn chạy đúng, do đồ thị trạng thái trong subtask này đối xứng. Sự tự tin ảo này khiến thí sinh nộp bài và sai khi gặp test ~X \neq Y~.

2. Cố gắng sử dụng đồng dư thức

Một số thí sinh đã cố gắng tìm công thức toán học ~O(1)~ hoặc ~O(N)~ dựa trên các tính chất đồng dư, và lại stress test kiểm chứng với subtask ~X = Y = 1~ (vốn rất dễ đúng với nhiều công thức sai). Một số thí sinh khác lại mất quá nhiều thời gian vào việc tìm công thức đồng dư.

Tuy vậy, nếu bài toán giải được bằng công thức toán, giới hạn ~N~ sẽ lớn hơn rất nhiều (~10^5~ chẳng hạn) chứ không dừng lại ở con số ~666~. Ta dễ dàng nhận thấy input thật sự của mỗi truy vấn chỉ là số bóng đèn của từng loại (do thứ tự không quan trọng). Vì vậy, ý tưởng đồng dư thức cần được gạch bỏ trong đầu ngay lập tức.

3. Lỗi về bộ nhớ

Không gian trạng thái của bài toán là ~O(N^3)~. Có người đã rất hồn nhiên khai báo mảng dist[666][666][666] kiểu int. ~666^3 \times 4~ bytes là khoảng ~1.18~ ~GB~. Trong khi đó, giới hạn bộ nhớ của đề bài là 1024 MB. Con số này sẽ càng thảm họa hơn nếu thí sinh có thói quen #define int long long.

Đây là lỗi sai rất nguy hiểm, chỉ xếp sau lỗi sai tên file về mức độ nghiêm trọng. Các bài nộp này sẽ nhận 0 điểm tròn vì MLE (Themis sẽ báo Chạy sinh lỗi khi chấm). Điểm chết người ở đây là lỗi này rất khó phát hiện khi code trên CodeBlocks hay các IDE khác, do các IDE này thường tận dụng toàn bộ dung lượng RAM vật lý của máy (thường là 8GB, 16GB...) để cấp phát, nên chương trình vẫn chạy khi test thử.

Vì vậy, các bạn cần rất cẩn thận với các mảng lớn bất thường. Tốt nhất là luôn đo bộ nhớ sau khi code xong.

Bài 2 - Quà Noel (GIFT)

Đây là bài toán mang hơi hướng tham lam kết hợp chặt nhị phân khá cổ điển. Tưởng chừng như là bài gỡ điểm sau bài 1 đầy sóng gió, nhưng bài này lại chứa đựng những ngộ nhận sai lầm về thuật toán, cũng như các lỗi sai trong cài đặt.

1. Ngộ nhận tham lam

Với các câu hỏi loại 2 (tìm số túi tối đa), một luồng tư duy rất tự nhiên là: Cứ nhặt ~K~ món nhỏ nhất ghép thành 1 túi, lặp lại cho đến khi không ghép được nữa.

Đây là bài toán tối ưu cục bộ không dẫn đến tối ưu toàn cục. Việc cố gắng làm cho túi hiện tại nhẹ nhất có thể làm lãng phí các món đồ nhỏ - những món quan trọng để lấp đầy các túi sau này (đặc biệt khi kết hợp với các món đồ lớn).

Cách làm tham lam này có thể vẫn qua được Subtask 2 và 4 (do đáp án chỉ là 0 hoặc 1, không gian mẫu nhỏ nên tham lam dễ trúng). Tuy nhiên, khi sang Subtask 3 và 5 (cần tối đa hóa số lượng túi), thuật toán này sẽ rất dễ sai.

2. Lỗi chỉ số mảng trong Subtask 1

Subtask 1 cho ~S_i = 1~ (mỗi loại chỉ có 1 món). Bài toán quy về: Kiểm tra xem tổng trọng lượng của ~K \times T~ món đồ nhẹ nhất có ~\le M~ hay không. Nhiều bạn đã sort mảng trọng lượng, dựng mảng cộng dồn và kiểm tra điều kiện ngay lập tức bằng cách truy cập mảng prefix_sum tại chỉ số k * t.

Tuy vậy, dù rằng ~N \le 10^5~, nhưng ta lại có ~K, T \le 10^9~. Tích ~K \times T~ hoàn toàn có thể lên tới ~10^{18}~. Khi truy cập vào prefix_sum[k * t] mà không xử lý trường hợp k * t > n trước, tỷ lệ ~0~ điểm là rất cao, do bài này gồm ~Q~ truy vấn.

Vì vậy, luôn phải kiểm tra điều kiện của từng biến trước khi code. Không nên tin tưởng vào trực giác để rồi mất điểm không đáng.

3. Lỗi tên file

Có lẽ do không khí Giáng sinh tràn ngập trong đề bài, hoặc do thói quen đọc tiêu đề bài (Quà Noel) thay vì mã bài, đã có người nhanh tay đặt tên file nộp là NOEL.CPP. Các thí sinh này ngay lập tức nhận ~0~ điểm tròn trĩnh.

Vì vậy, các thí sinh luôn cần nhìn vào bảng Tổng quan đề thi (trang 1) để lấy mã bài chính xác cho từng bài.

Bài 3 - Bài đăng (POST)

Đây là một bài toán cấu trúc dữ liệu điển hình (có thể giải bằng Mo's algorithm, Segment Tree, Sweep Line hoặc các kỹ thuật xử lý truy vấn offline). Tuy nhiên, cái chết đến từ việc đánh giá sai độ lớn của dữ liệu đầu vào.

Nhìn vào bảng Subtask, ta thấy:

  • Subtask 2: ~N \le 500~.
  • Subtask 3: ~N \le 5000~.

Theo phản xạ thông thường của các VOI-er, ~N~ nhỏ thường đi kèm với thuật toán ~O(N^2 \times Q)~ hoặc ~O(N \times Q)~. Đã có nhiều bài nộp cài đặt thuật toán duyệt trâu ~O(N \times Q)~ hoặc ~O(N^2 \times Q)~ với niềm tin rằng: Chắc ~Q~ cũng nhỏ thôi.

Tuy vậy, đề bài không giới hạn ~Q~ nhỏ ở các subtask này. ~Q~ hoàn toàn có thể lên tới ~3 \times 10^5~. Với ~N = 500~ và ~Q = 3 \times 10^5~, độ phức tạp ~N \times Q \approx 1.5 \times 10^8~ phép tính. Nếu hằng số thuật toán lớn, khả năng TLE là rất cao. Với subtask ~N = 5000~ thì chắc chắn quá thời gian.

Vì vậy, thí sinh cần sử dụng mảng cộng dồn 2 chiều cho subtask này. Đồng thời, đừng bao giờ đoán mò giới hạn theo trực giác. Hãy đọc kỹ dòng giới hạn của từng Subtask. Nếu đề không ghi giới hạn cho một số biến, mặc định các biến này sẽ có giới hạn theo đề bài gốc.

Bài 4 - Tất niên (TET)

Nếu bài 1 là cú đấm về bộ nhớ, thì bài 4 là đòn đánh tâm lý cực mạnh vào niềm tin của thí sinh. Đây là ví dụ kinh điển cho việc: Code chạy đúng 1000 test sinh ngẫu nhiên, nhưng vẫn 0 điểm khi chấm thật.

1. Đọc lướt đề

Yêu cầu đề bài: Sau khi nhấc đĩa ~p_j~, tính độ đa dạng lớn nhất trong các đoạn con còn lại. Tuy vậy, có bạn đọc nhanh và cài đặt theo hướng tính tổng độ đa dạng của các đoạn con. Hoặc ngược lại, có trường hợp không tính đoạn gốc (không thực hiện đảo ngược đoạn con nào).

Trớ trêu thay, nếu kết hợp hai cách hiểu đề sai trên (tính tổng + không tính đoạn gốc), kết quả lại khớp với test ví dụ. Điều này càng củng cố cho một chiến thuật tối quan trọng: Hãy tận dụng triệt để 10 phút đọc đề đầu giờ.

Theo quy chế, từ 7h50 đến 8h00 là thời gian cho việc đọc toàn bộ đề và kiểm tra lỗi in ấn. Tuy nhiên, một số thí sinh nóng vội đã code ngay từ 7h51. Sự vội vã này không giúp các bạn tiết kiệm thêm được bao nhiêu thời gian, nhưng lại tước đi cái nhìn tổng quan về các bài sau, dẫn đến việc đọc lướt và bỏ sót các chi tiết tử huyệt như trong bài toán này, hay thậm chí dẫn đến sai lầm trong chiến thuật làm bài tổng thể.

2. Ngộ nhận thuật toán

Đây là lỗi sai "đẳng cấp" nhất trong kỳ thi năm nay, xảy ra với cả những bạn có kỹ năng lập trình rất tốt.

Bài toán yêu cầu đếm số lượng dãy khác nhau tạo được (bằng tối đa 1 phép đảo). Bản chất bài toán này là, một đoạn ~[L, R]~ khi đảo ngược sẽ tạo ra dãy mới khi và chỉ khi ~A[L] \neq A[R]~ (do xét đệ quy vào trong cũng tạo ra một đoạn tương tự). Ngược lại, có bạn lại ngộ nhận rằng: Độ đa dạng liên quan trực tiếp đến số lượng đoạn con đối xứng.

Điều này có thể nhận ra (và sửa lại) bằng cách kiểm thử. Đúng. Nhưng hãy cùng bắt đầu một quy trình dẫn tới 0 điểm:

  • Trong code chính, sử dụng Manacher hoặc Hash để đếm số lượng đoạn con đối xứng (theo logic sai nói trên).
  • Trong code trâu, thay vì làm đúng định nghĩa đề bài (sinh các cách đảo, bỏ vào std::set), thí sinh lại viết một code ~O(N^3)~ để ... đếm Palindrome trâu.
  • Như vậy, code chính và code trâu khớp nhau 100% trên hàng nghìn test. Thí sinh tự tin nộp bài. Nhưng sự thật thì, cả hai code đều đang sai bản chất toán học của đề bài. Các thí sinh sẽ nhận ~0~ điểm.

Chính vì vậy, code trâu khi kiểm thử luôn phải là code thuần túy nhất. Hãy mô phỏng toàn bộ đề bài, đừng sử dụng bất cứ nhận xét nào.

Ngay cả khi logic đúng, nhiều bạn vẫn mất điểm vì trình sinh test quá yếu. Các thí sinh này đã thực hiện stress test bằng cách sinh mảng ~n~ số ngẫu nhiên từ ~1~ đến ~10^9~. Như vậy, mảng sinh ra toàn các số phân biệt. Lúc này, hầu hết các thuật toán (dù cài đặt có lỗi) đều chạy đúng (vì không có sự trùng lặp để làm thay đổi đáp án).

Điều này củng cố cho việc: sinh test không chỉ đơn giản là random, sinh test phải thật mạnh để có thể nhận ra các lỗi sai.

3. Chỉ sử dụng 1 mod khi Hash

Một vài bạn có mục tiêu giải số đã quyết định dừng lại ở subtask 1 và 2 trong bài này. Trong số đó, nhiều bạn dùng Hashing để check nhanh, nhưng chỉ sử dụng 1 modulo cỡ ~10^9~ để tránh tràn số.

Theo Birthday Paradox, chỉ cần số phần tử trong tập ~\ge \sqrt {mod}~ là đã có tỷ lệ sai khá cao rồi. Với ~N = 200~, số đoạn con cần quan tâm là khoảng ~N^2 = 40000~, đã không đủ an toàn. Chưa nói đến các subtask lớn hơn như ~N = 2000~.

Vì vậy, trong các kỳ thi offline, cần sử dụng modulo cỡ ~10^{18}~, tuy vậy, để đơn giản hơn, ta nên sử dụng hai modulo cỡ ~10^9~.

Bài 5 - Cây thông (PINE)

Đây là bài cấu trúc dữ liệu hạng nặng của ngày 2. Tuy nhiên, chưa bàn đến thuật toán cao siêu như HLD (Heavy-Light Decomposition) cùng với Segment Tree, rất nhiều thí sinh đã mắc sai lầm ngay từ khâu đọc đề và xử lý các subtask cơ bản.

1. Subtask 2 - Đường thẳng và những cái bẫy

Subtask 2 cho cây có dạng đường thẳng. Nhiều bạn đã sử dụng Segment Tree để ăn trọn điểm phần này. Trong số đó, nhiều thí sinh đã giả định rằng cây đường thẳng nghĩa là cạnh sẽ luôn nối ~(i, i+1)~ và input sẽ luôn cho theo thứ tự đó. Tuy vậy, đề bài chỉ nói cấu trúc cây là đường thẳng từ ~1~ tới ~N~, nhưng input vẫn tuân theo format chung: ~N - 1~ dòng chứa cặp ~(u, v)~. Các cạnh có thể bị đảo lộn thứ tự (ví dụ: dòng 1 cho cạnh 3-4, dòng 2 cho cạnh 1-2). Các đỉnh trong một cạnh cũng không nhất thiết theo thứ tự tăng dần (ví dụ: 3 2 thay vì 2 3).

Đồng thời, ở phần truy vấn, ~u~ và ~v~ cũng là bất kỳ, không nhất thiết ~u \le v~. Nếu dùng Segment Tree mà cập nhật ngay seg.update (u, v) thì thuật toán sẽ không còn chạy đúng nữa.

2. Vòng lặp vô tận với BIT

Ở Subtask 3 (~x_t = y_t~), và xuyên suốt bài toán, một cách giải phổ biến là dùng BIT để duy trì mảng tần số ~cnt_v~ – đếm số lượng tấm thiệp đang có giá trị độ đẹp là ~v~.

Dữ liệu đề bài cho giá trị độ đẹp ~A_i \ge 0~, tức là số 0 hoàn toàn có thể xuất hiện. Khi cần cập nhật cho một tấm thiệp có giá trị 0 (tăng tần số của số 0 lên 1), một vài bạn đã gọi update (0, 1). Lúc này, chương trình rơi vào vòng lặp vô tận và nhận kết quả TLE, do bit nhỏ nhất của ~0~ vẫn là ~0~, dù độ phức tạp thuật toán về lý thuyết là chuẩn.

Vì vậy, khi sinh test, luôn cần quan tâm các trường hợp sát biên (các biến được đẩy lên cao nhất / thấp nhất) để tránh các lỗi sai này.

Bài 6 - Cắm trại (CAMP)

Đây là bài cuối cùng, và thường là bài khó debug nhất (vì làm việc với số thực và hình học). Debug một bài hình học luôn là điều cấm kỵ và rủi ro với mọi thí sinh. Nhiều bạn dành quá nhiều thời gian (1-2 tiếng) để cố gắng cắn nhiều điểm nhất của bài này, trong khi các subtask của bài 4 và 5 vẫn chưa vét hết.

Bài hình học, lại còn là bài cuối luôn là bài Boss của đề thi. Việc đầu tư quá nhiều thời gian vào đây thường dẫn đến tình trạng trắng tay: bài 6 không có nhiều điểm, bài 4, 5 thì thiếu thời gian làm trâu hoặc sai các lỗi nói trên.

Lời kết

Vậy là VOI 2026 đã chính thức trở thành quá khứ. Những lỗi sai như đặt nhầm tên file NOEL.CPP, đọc sai đề, hay hash yếu... là những bài học đắt giá nhưng cần thiết để trưởng thành. Thể thức thi Offline khắc nghiệt không chỉ kiểm tra kiến thức mà còn thử thách sự cẩn trọng đến mức cực đoan và bản lĩnh của bạn.

Đừng quá dằn vặt nếu kết quả không như ý. Một chiếc huy chương có thể đổi màu vì một dòng code, nhưng trình độ và tư duy của bạn thì không ai lấy đi được. Hãy cười xòa một cái, đóng laptop lại và ăn một cái Tết thật ngon.

Với các bạn lớp 12, hành trình VOI của các bạn đã khép lại tại đây. Sẽ có những tiếc nuối, sẽ có những cái giá như, nhưng đừng để nó dằn vặt bạn quá lâu. Kết thúc kỳ thi cũng là lúc chúng ta cởi bỏ chiếc áo đội tuyển để trở về với lớp học thân quen. Hãy tận hưởng những ngày cuối cùng trong thời học sinh thật rực rỡ của bạn.

Với các bạn lớp 10 và 11, nếu kết quả chưa như ý, hãy coi những lỗi sai phía trên là những bài học, và biến nó thành bộ giáp vững chắc cho mùa giải sau, nơi các bạn có thể cháy hết mình một lần nữa.

Cuối cùng, dù bạn là ai, kết quả thế nào, xin hãy khắc cốt ghi tâm câu thần chú của các VOI-er:

Trong kỳ thi VOI với thể thức Offline khắc nghiệt, người chiến thắng không hẳn là người giải được nhiều bài khó nhất, mà là người mắc ít lỗi sai ngớ ngẩn nhất.


Bình luận

Hãy đọc nội quy trước khi bình luận.



  • 0
    NguyenKhangNinh_69  đã bình luận lúc 10, Tháng 2, 2026, 13:29

    bài viết rất hữu ích ạ


  • -1
    haductrithcstq  đã bình luận lúc 10, Tháng 2, 2026, 13:03

    post rat tuff