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 →

Xếp Khối Gỗ (Thi thử HSG9 SQRTOJ 2025 – Ngày 1 Bài 1)

Xếp Khối Gỗ (Thi thử HSG9 SQRTOJ 2025 – Ngày 1 Bài 1)
Nguồn: Thi thử HSG9 SQRTOJ 2025 – Ngày 1 Bài 1 Độ khó: Trung bình Toán học BCNN – ƯCLN Đếm Map

Đề bài

XẾP KHỐI GỖ (7 điểm)

Quang có $n$ khối gỗ màu đỏ, mỗi khối có kích thước $1 \times 1 \times a$ (cm) và $m$ khối gỗ màu xanh, mỗi khối có kích thước $1 \times 1 \times b$ (cm). Quang xếp các khối gỗ thành một cột thẳng đứng có đáy là một hình vuông kích thước $1 \times 1$ (cm) và tiến hành đo chiều cao của cột gỗ, ký hiệu là $h$. Quang thử tất cả các cách xếp khác nhau và ghi lại các giá trị $h$ thu được.

Yêu cầu: Vì số lượng giá trị $h$ mà Quang ghi lại được là quá lớn, bạn hãy giúp Quang đếm xem có bao nhiêu giá trị $h$ khác nhau mà Quang đã tạo ra.

Dữ liệu vào: Nhập từ file WOOD.inp:

  • Một dòng duy nhất gồm bốn số nguyên dương $a, b, n, m$ $(1 \le a, b, n, m \le 10^9)$.

Dữ liệu ra: Ghi ra file WOOD.out:

  • Một dòng duy nhất là số giá trị $h$ khác nhau mà Quang tạo được. Vì số giá trị có thể rất lớn nên chỉ cần in ra phần dư của nó khi chia cho $10^9 + 7$.

Ràng buộc bổ sung:

  • $20\%$ số điểm có $n = m = 1$.
  • $20\%$ số điểm khác có $a = b$.
  • $10\%$ số điểm khác có $m = 1$.
  • $10\%$ số điểm khác có $n, m \le 10$.
  • $10\%$ số điểm khác có $n, m \le 1000$.
  • $10\%$ số điểm khác có $b = 1$.
  • $10\%$ số điểm khác có $m \le 10^5$.
  • $10\%$ số điểm còn lại không có giới hạn gì thêm.

Lưu ý: đề gốc đọc/ghi file WOOD.inp / WOOD.out; nộp trên web thì đọc từ bàn phím, in ra màn hình (code bên dưới có đoạn if(fopen(...)) nên nộp kiểu nào cũng chạy đúng).

WOOD.inp (ví dụ 1)
2 3 1 2
WOOD.out (ví dụ 1)
5
WOOD.inp (ví dụ 2)
3 3 1 1
WOOD.out (ví dụ 2)
2

Giải thích (theo đề):

  • Ví dụ 1: các giá trị $h$ có thể tạo được là $2, 3, 5, 6, 8$. Để tạo được giá trị $h = 6$, có thể chồng hai khối gỗ màu xanh lên nhau; để tạo được giá trị $h = 5$, có thể chồng một khối gỗ màu đỏ lên một khối gỗ màu xanh.
  • Ví dụ 2: có thể tạo ra được 2 giá trị $h = 3$ và $h = 6$.

Lời mở đầu

Đây là bài 1 ngày thi thứ nhất trong kỳ thi thử HSG lớp 9 năm 2025 của câu lạc bộ SQRT. Đề có tới 8 mức ràng buộc nhỏ, nên mình viết lời giải đi lên theo từng subtask để các bạn thấy cách "ăn điểm" từng phần rồi mới tới lời giải trọn vẹn nha. Hy vọng mang đến được giá trị cho các bạn 😇.

Mình giải thích ví dụ để bạn nào chưa hiểu đề sẽ hiểu rõ. Nếu Quang dùng $x$ khối đỏ ($0 \le x \le n$) và $y$ khối xanh ($0 \le y \le m$) thì chiều cao cột là $h = a \cdot x + b \cdot y$ – thứ tự xếp không quan trọng. Cột phải có ít nhất 1 khối, nên ta bỏ trường hợp $x = y = 0$. Ở ví dụ 1: $a = 2$, $b = 3$, $n = 1$, $m = 2$:

h = 2 h = 3 h = 5 h = 6 h = 8
Đỏ cao $a = 2$, xanh cao $b = 3$. Có $2 \times 3 - 1 = 5$ cách chọn $(x, y)$ khác $(0, 0)$ và 5 chiều cao này đều khác nhau ⇒ đáp án $5$.

Còn ví dụ 2 có $a = b = 3$, $n = m = 1$: các cách chọn cho $h = 3$ (1 đỏ), $h = 3$ (1 xanh), $h = 6$ (1 đỏ + 1 xanh). Hai cách đầu trùng chiều cao nên chỉ còn 2 giá trị. Cái khó của bài chính là đếm sao cho các chiều cao trùng nhau chỉ được tính 1 lần.

Link nộp bài

Bấm vào nút bên dưới để nộp bài và được hệ thống CLUE OJ chấm tự động.

Nộp bài trên CLUE OJ

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

Subtask 1n = m = 1 – O(1)

Nếu $n = m = 1$ thì mỗi màu chỉ có 1 khối, các chiều cao có thể là $a$ (1 khối đỏ), $b$ (1 khối xanh) và $a + b$ (cả hai). Ta có hai trường hợp sau:

  • $a \ne b$: 3 trường hợp $a$, $b$, $a + b$. Ta nhận thấy rằng $a + b \gt a$ và $a + b \gt b$ nên không có trường hợp số này xuất hiện quá 1 lần. Xét hai số còn lại, $a \ne b$ nên hai số này cũng khác nhau. Do đó kết quả trong trường hợp này là 3.
  • $a = b$: 3 trường hợp $a$, $a$, $2a$. Ta nhận thấy rằng $2a \gt a$ nên không có trường hợp số này xuất hiện quá 1 lần. Xét hai số còn lại, $a = b$ nên hai số này bị trùng nhau. Do đó kết quả trong trường hợp này là 2.

Source code để các bạn tham khảo nếu làm theo hướng này:

#include<bits/stdc++.h>
#define int long long
using namespace std;

int a, b, n, m;

signed main(){
    if(fopen("WOOD.inp", "r")){
        freopen("WOOD.inp", "r", stdin);
        freopen("WOOD.out", "w", stdout);
    }
    cin >> a >> b >> n >> m;
    if (a == b) cout << 2 << endl;
    else cout << 3 << endl;
}
import sys, os

if os.path.exists('WOOD.inp'):
    sys.stdin = open('WOOD.inp')
    sys.stdout = open('WOOD.out', 'w')
a, b, n, m = map(int, sys.stdin.read().split())
if a == b:
    print(2)
else:
    print(3)

Các bạn bấm Tiếp để chạy tay. Thử luôn nút "Ví dụ 1 (n, m ≠ 1)" để thấy code này in sai khi không còn $n = m = 1$ nữa nha:

C++▶ Chạy tay từng dòng codeSubtask 1: n = m = 1 nên chỉ có các chiều cao a, b, a + b; a = b thì trùng 1 cặp.
Dữ liệu mẫu:
1#include<bits/stdc++.h>
2#define int long long
3using namespace std;
4
5int a, b, n, m;
6
7signed main(){
8 if(fopen("WOOD.inp", "r")){
9 freopen("WOOD.inp", "r", stdin);
10 freopen("WOOD.out", "w", stdout);
11 }
12 cin >> a >> b >> n >> m;
13 if (a == b) cout << 2 << endl;
14 else cout << 3 << endl;
15}
Không gian$O(1)$
Thời gian$O(1)$

Subtask 2a = b – O(1)

Nếu $a = b$, ta có $h = a \cdot x + b \cdot y = a \cdot (x + y)$.

Khác biệt duy nhất giữa các số $h$ sẽ là phần $(x + y)$. Do đó, ta sẽ đếm thử với $0 \le x \le n$, $0 \le y \le m$ thì có bao nhiêu giá trị $(x + y)$ phân biệt. Dễ thấy $x + y$ nhận được mọi giá trị nguyên từ $0$ đến $n + m$ (cứ tăng $x$ lên tới $n$ rồi tăng $y$), nên có $n + m + 1$ giá trị. Trừ đi trường hợp tổng bằng $0$ (không có khối nào) thì kết quả sẽ là:

Công thức subtask 2
Khi $a = b$: đáp án $= (n + m) \bmod (10^9 + 7)$.

Source code để các bạn tham khảo nếu làm theo hướng này:

#include<bits/stdc++.h>
#define int long long
using namespace std;

int a, b, n, m;
int MOD = 1e9 + 7;

signed main(){
    if(fopen("WOOD.inp", "r")){
        freopen("WOOD.inp", "r", stdin);
        freopen("WOOD.out", "w", stdout);
    }
    cin >> a >> b >> n >> m;
    if (a == b) cout << (n + m) % MOD << endl;
}
import sys, os

MOD = 10**9 + 7
if os.path.exists('WOOD.inp'):
    sys.stdin = open('WOOD.inp')
    sys.stdout = open('WOOD.out', 'w')
a, b, n, m = map(int, sys.stdin.read().split())
if a == b:
    print((n + m) % MOD)
C++▶ Chạy tay từng dòng codeSubtask 2: a = b nên h = a·(x + y), x + y chạy từ 1 tới n + m ⇒ có n + m giá trị.
Dữ liệu mẫu:
1#include<bits/stdc++.h>
2#define int long long
3using namespace std;
4
5int a, b, n, m;
6int MOD = 1e9 + 7;
7
8signed main(){
9 if(fopen("WOOD.inp", "r")){
10 freopen("WOOD.inp", "r", stdin);
11 freopen("WOOD.out", "w", stdout);
12 }
13 cin >> a >> b >> n >> m;
14 if (a == b) cout << (n + m) % MOD << endl;
15}
Không gian$O(1)$
Thời gian$O(1)$

Subtask 4 + 5Duyệt mọi cặp (x, y) và lưu vào map – O(n·m·log(n·m))

Vì $n, m$ khá nhỏ ($\le 1000$) nên ta sẽ xét tất cả giá trị $h = a \cdot x + b \cdot y$ của các cặp $(x, y)$ với $0 \le x \le n$ và $0 \le y \le m$. Ta có thể sử dụng map để lưu các giá trị $h$ (map tự bỏ trùng) và lấy size của map này. Lưu ý nhớ trừ đi 1 để tránh trường hợp $h = 0$ (khi $x = y = 0$).

Source code để các bạn tham khảo nếu làm theo hướng này:

#include<bits/stdc++.h>
#define int long long
using namespace std;

int MOD = 1e9 + 7;
int a, b, n, m;

signed main(){
    if(fopen("WOOD.inp", "r")){
        freopen("WOOD.inp", "r", stdin);
        freopen("WOOD.out", "w", stdout);
    }
    cin >> a >> b >> n >> m;
    map<int, int> cnt;
    for (int i = 0; i <= n; i++){
        for (int j = 0; j <= m; j++){
            cnt[a * i + j * b] = 1;
        }
    }
    cout << cnt.size() - 1 << endl;
}
import sys, os

if os.path.exists('WOOD.inp'):
    sys.stdin = open('WOOD.inp')
    sys.stdout = open('WOOD.out', 'w')
a, b, n, m = map(int, sys.stdin.read().split())
cnt = set()
for i in range(n + 1):
    for j in range(m + 1):
        cnt.add(a * i + j * b)
print(len(cnt) - 1)

Bản Python dùng set thay cho map vì ta chỉ cần biết giá trị nào đã xuất hiện. Các bạn chạy tay để xem map cnt lớn dần và các giá trị trùng bị "nhập chung" như thế nào:

C++▶ Chạy tay từng dòng codeSubtask 4 + 5: duyệt mọi (i, j), cho a·i + j·b vào map; đáp án là cnt.size() - 1 (bỏ h = 0).
Dữ liệu mẫu:
1#include<bits/stdc++.h>
2#define int long long
3using namespace std;
4
5int MOD = 1e9 + 7;
6int a, b, n, m;
7
8signed main(){
9 if(fopen("WOOD.inp", "r")){
10 freopen("WOOD.inp", "r", stdin);
11 freopen("WOOD.out", "w", stdout);
12 }
13 cin >> a >> b >> n >> m;
14 map<int, int> cnt;
15 for (int i = 0; i <= n; i++){
16 for (int j = 0; j <= m; j++){
17 cnt[a * i + j * b] = 1;
18 }
19 }
20 cout << cnt.size() - 1 << endl;
21}

Đánh giá độ phức tạp của cách làm này:

  • Không gian: map chứa tối đa $(n + 1)(m + 1)$ giá trị: $O(n \cdot m)$.
  • Thời gian: có $(n + 1)(m + 1)$ lần thêm vào map, mỗi lần $O(\log(n \cdot m))$: tổng $O(n \cdot m \cdot \log(n \cdot m))$. Với $n = m = 1000$ là khoảng $10^6$ lần thêm.
Không gian$O(n \cdot m)$
Thời gian$O(n \cdot m \cdot \log(n \cdot m))$
Chỉ dùng được khi n, m nhỏ
Mình đo thử bản C++ với $n = m = 1000$ và $a, b$ "khó" (gần như không có giá trị trùng, map phải chứa $10^6$ khoá): khoảng 1.2 giây; còn $n = m = 10^4$ thì mất gần 8 giây. Các subtask còn lại có $n$ hoặc $m$ tới $10^9$ thì duyệt là không thể – phải tìm công thức!

Subtask 3 + 6 + 7 + 8Đếm bằng BCNN – O(log(min(a, b)))

Ta sẽ có hướng như sau: đối với mỗi $x$ (số khối đỏ), ta sẽ tìm số lượng số $y$ sao cho thu được $h = a \cdot x + b \cdot y$ mà không thể thu được từ một cặp $(u, v)$ có $u \lt x$. Nói cách khác, mỗi chiều cao $h$ ta chỉ đếm đúng một lần tại cách xếp dùng ít khối đỏ nhất.

Để tính toán, ta sẽ đảo ngược lại bằng cách xem xét thử với mỗi cặp $(x, y)$ thì có bao nhiêu cặp $(u, v)$ với $u \lt x$ mà $a \cdot u + b \cdot v = a \cdot x + b \cdot y$. Đẳng thức này tương đương:

Biến đổi
$a \cdot (x - u) = b \cdot (v - y)$, với $x - u \gt 0$ nên $v - y \gt 0$ (bớt khối đỏ thì phải thêm khối xanh).

Vế trái chia hết cho $a$, vế phải chia hết cho $b$, nên giá trị chung này là một bội chung của $a$ và $b$. Để đạt được điều này, ta nhận thấy $x - u$ và $v - y$ nên nhỏ nhất có thể (vẫn phải lớn hơn $0$) ⇒ $a \cdot (x - u) = b \cdot (v - y) = \text{BCNN}(a, b)$.

Do đó $(x - u)$ và $(v - y)$ nhỏ nhất là một hằng số. Ta đặt:

Hai bước nhảy cố định
$L = \text{BCNN}(a, b) = \dfrac{a \cdot b}{\text{ƯCLN}(a, b)}$, $\quad dis\_ = \dfrac{L}{a}$, $\quad dis = \dfrac{L}{b}$.
Bớt đi $dis\_$ khối đỏ và thêm $dis$ khối xanh thì chiều cao không đổi (vì $a \cdot dis\_ = b \cdot dis = L$).

Khi này $u = x - dis\_$ và $v = y + dis$ phải thoả mãn cả hai điều kiện sau:

  • $u \ge 0$: nếu $x \lt dis\_$ thì $u \lt 0$ và điều này không thoả mãn điều kiện.
  • $v \le m$: nếu $y \gt m - dis$ thì $v \gt m$ và điều này không thoả mãn điều kiện.
Vì sao chỉ cần xét bước nhảy nhỏ nhất?
Mọi cặp $(u, v)$ hợp lệ đều có dạng $u = x - t \cdot dis\_$, $v = y + t \cdot dis$ với $t \ge 1$. Nếu có một cặp như vậy thì cặp ứng với $t = 1$ cũng hợp lệ, vì $x - dis\_ \ge x - t \cdot dis\_ \ge 0$ và $y + dis \le y + t \cdot dis \le m$. Ngược lại, nếu bước $t = 1$ không làm được thì không bước nào làm được.

Từ trên, về lại bài toán gốc: với mỗi $x$, những $y$ cho ra chiều cao "mới" (không thu được từ $u \lt x$) là những $y$ làm một trong hai điều kiện trên không được thoả mãn. Vậy kết quả chia làm hai trường hợp:

  • Nếu $x \lt dis\_$: mọi số $y$ đều thoả mãn (đã có một điều kiện bị vi phạm nên không cần quan tâm đến $y$). Số lượng $y$ cho mỗi $x$ như vậy là $m + 1$, và có $\min(n + 1, dis\_)$ giá trị $x$ như vậy.
  • Nếu $x \ge dis\_$: cần $y \gt m - dis$, tức $y$ thuộc $[\max(0, m - dis + 1), m]$, dễ thấy số lượng $y$ như vậy cho mỗi $x$ là $\min(m + 1, dis)$. Có $(n + 1) - \min(n + 1, dis\_)$ giá trị $x$ như vậy.
Công thức tổng quát
$\text{đáp án} = \min(n + 1, dis\_) \cdot (m + 1) + \big(n + 1 - \min(n + 1, dis\_)\big) \cdot \min(m + 1, dis) - 1$
Lưu ý: kết quả tổng quát sẽ trừ đi $1$ để tránh trường hợp $h = 0$ (cặp $(0, 0)$ luôn được đếm ở trường hợp đầu).

Kiểm tra với ví dụ 1: $a = 2$, $b = 3$ ⇒ $L = 6$, $dis\_ = 3$, $dis = 2$. Vì $n + 1 = 2 \lt 3$ nên mọi $x$ đều thuộc trường hợp đầu: $2 \cdot 3 - 1 = 5$ ✔. Ví dụ 2: $a = b = 3$ ⇒ $L = 3$, $dis\_ = dis = 1$: $1 \cdot 2 + 1 \cdot 1 - 1 = 2$ ✔.

Minh hoạ: ô nào được đếm?

Khung dưới vẽ bảng chiều cao $h = a \cdot x + b \cdot y$: hàng là $x$, cột là $y$. Bấm Tiếp để xét từng hàng $x$: ô xanh là chiều cao mới được đếm, ô xám là chiều cao đã có ở hàng $x - dis\_$ (ghi rõ trùng với ô nào). Thử nút "a = 4, b = 6" để thấy rõ các ô trùng nha:

Ví dụ nhanh:

Source code để các bạn tham khảo nếu làm theo hướng này:

#include<bits/stdc++.h>
#define int long long
using namespace std;

int a, b, n, m;
long long MOD = 1e9 + 7;

signed main(){
    if(fopen("WOOD.inp", "r")){
        freopen("WOOD.inp", "r", stdin);
        freopen("WOOD.out", "w", stdout);
    }
    cin >> a >> b >> n >> m;
    int need_ = a * b / __gcd(a, b);
    int dis = need_ / b;
    int dis_ = need_ / a;
    long long res = 0;
    res += min(n + 1, dis_) * (m + 1);
    res %= MOD;
    res += (n + 1 - min(n + 1, dis_)) * min(m + 1, dis);
    res %= MOD;
    cout << (res - 1 + MOD) % MOD;
}
import sys, os
from math import gcd

MOD = 10**9 + 7
if os.path.exists('WOOD.inp'):
    sys.stdin = open('WOOD.inp')
    sys.stdout = open('WOOD.out', 'w')
a, b, n, m = map(int, sys.stdin.read().split())
need_ = a * b // gcd(a, b)
dis = need_ // b
dis_ = need_ // a
res = 0
res += min(n + 1, dis_) * (m + 1)
res %= MOD
res += (n + 1 - min(n + 1, dis_)) * min(m + 1, dis)
res %= MOD
print((res - 1 + MOD) % MOD)

Giải thích 1 số chỗ trong code ở trên:

  1. need_ = a * b / __gcd(a, b) chính là $L = \text{BCNN}(a, b)$. Vì $a, b \le 10^9$ nên $a \cdot b \le 10^{18}$, vẫn nằm trong long long (tối đa khoảng $9.2 \cdot 10^{18}$) nên nhân trước chia sau vẫn an toàn.
  2. min(n + 1, dis_) * (m + 1) và (n + 1 - min(n + 1, dis_)) * min(m + 1, dis) mỗi tích tối đa cỡ $10^{18}$, nên mình lấy dư res %= MOD ngay sau mỗi lần cộng để không bị tràn.
  3. (res - 1 + MOD) % MOD: trừ 1 cho trường hợp $h = 0$; cộng thêm MOD phòng khi res sau khi lấy dư bằng $0$ thì không bị ra số âm.
C++▶ Chạy tay từng dòng codeSubtask 3, 6, 7, 8: dis_ = L/a, dis = L/b; hàng x < dis_ đếm cả m + 1 ô, các hàng còn lại đếm min(m + 1, dis) ô; trừ 1.
Dữ liệu mẫu:
1#include<bits/stdc++.h>
2#define int long long
3using namespace std;
4
5int a, b, n, m;
6long long MOD = 1e9 + 7;
7
8signed main(){
9 if(fopen("WOOD.inp", "r")){
10 freopen("WOOD.inp", "r", stdin);
11 freopen("WOOD.out", "w", stdout);
12 }
13 cin >> a >> b >> n >> m;
14 int need_ = a * b / __gcd(a, b);
15 int dis = need_ / b;
16 int dis_ = need_ / a;
17 long long res = 0;
18 res += min(n + 1, dis_) * (m + 1);
19 res %= MOD;
20 res += (n + 1 - min(n + 1, dis_)) * min(m + 1, dis);
21 res %= MOD;
22 cout << (res - 1 + MOD) % MOD;
23}

Đánh giá độ phức tạp của cách làm này:

  • Không gian: chỉ vài biến: $O(1)$.
  • Thời gian: tính ƯCLN bằng thuật toán Euclid tốn $O(\log(\min(a, b)))$, phần còn lại là vài phép tính: tổng $O(\log(\min(a, b)))$ – chạy tức thì với mọi $a, b, n, m \le 10^9$.
Không gian$O(1)$
Thời gian$O(\log(\min(a, b)))$
Công thức này "ăn" luôn mọi subtask
Thực ra công thức cuối cùng đúng cho mọi trường hợp, kể cả $n = m = 1$ hay $a = b$ (khi đó $dis\_ = dis = 1$, công thức cho $1 \cdot (m + 1) + n \cdot 1 - 1 = n + m$, trùng với subtask 2). Mình vẫn để các subtask nhỏ ở trên vì trong phòng thi, làm chắc các phần dễ trước là chiến thuật rất quan trọng đó các bạn.
Ghi chú của mình: code C++ và Python của cả 4 phần đã được kiểm tra với 800 bộ test ngẫu nhiên nhỏ ($a, b \le 30$, $n, m \le 40$), so với code vét cạn đếm trực tiếp mọi $a \cdot x + b \cdot y$ – khớp hết (code subtask 1, 2 chỉ so trên các test đúng subtask của nó). Công thức cuối còn được so thêm với cách đếm trực tiếp trên 300 bộ $n, m \le 300$. Test lớn: code subtask 4 + 5 với $n = m = 1000$ chạy khoảng 1.2 giây (C++) và 0.8 giây (Python); code cuối với $a, b, n, m = 10^9$ chạy tức thì.
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 😇
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