Vỏ lãi (Đề tự luyện HSG Tin quốc gia – Bài 3)
Đề bài
Vỏ lãi. Thầy Sơn chở $N$ học trò qua sông bằng hai chiếc vỏ lãi; em thứ $i$ nặng $w_i$ kg, mỗi em ngồi đúng một chiếc (một chiếc có thể trống). Có $M$ ghi chú: loại $1$ "$u$ $v$" — hai em phải cùng chiếc; loại $2$ "$u$ $v$" — hai em phải khác chiếc. Tìm độ chênh lệch nhỏ nhất giữa tổng cân nặng hai chiếc khi thỏa mọi ghi chú, hoặc in $-1$ nếu không thể.
Dữ liệu vào: dòng 1: $N$, $M$; dòng 2: $w_1, \ldots, w_N$; $M$ dòng tiếp: $t$, $u$, $v$. $1 \le N \le 3 \cdot 10^5$, $0 \le M \le 3 \cdot 10^5$, $w_i \ge 1$, $\sum w_i \le 5 \cdot 10^6$.
Kết quả: độ chênh lệch nhỏ nhất, hoặc $-1$.
- Subtask 1 (15%): $N \le 20$.
- Subtask 2 (20%): $M = 0$, $N \le 2000$, $\sum w_i \le 2 \cdot 10^5$.
- Subtask 3 (25%): $N \le 2000$, $\sum w_i \le 2 \cdot 10^5$.
- Subtask 4 (40%): không có ràng buộc gì thêm.
Giới hạn thời gian: C++ 1 giây, Python/PyPy 3 giây.
8 4
5 4 2 4 3 7 4 9
2 6 1
1 6 4
2 5 7
1 7 82Chiếc thứ nhất chở các em $2, 4, 5, 6$ (tổng $4 + 4 + 3 + 7 = 18$), chiếc thứ hai chở các em $1, 3, 7, 8$ (tổng $5 + 2 + 4 + 9 = 20$). Mọi ghi chú đều thỏa, độ chênh $2$; không có cách nào chênh ít hơn.
Bạn nào muốn thử nộp bài để hệ thống chấm xem có đúng hay không thì dùng link này nhé. Bộ test có chia subtask và có cả những test "bẫy", nên code đúng ý tưởng nhưng sai chi tiết nhỏ cũng sẽ bị trừ điểm đó.
Nộp bài Vỏ lãiLời mở đầu
Nếu không có ghi chú nào, đây chính là bài chia kẹo quen thuộc: chọn một nhóm em có tổng cân nặng gần $\frac{S}{2}$ nhất (với $S$ là tổng cân nặng), làm bằng quy hoạch động cái túi. Ghi chú làm bài khó hơn ở hai chỗ, và cả bài là hành trình gỡ hai chỗ đó:
- Ghi chú ràng buộc các em thành từng nhóm. Coi mỗi em là một đỉnh, mỗi ghi chú là một cạnh. Trong một thành phần liên thông, chỉ cần biết chiếc của một em là suy ra chiếc của tất cả các em còn lại (cạnh loại 1 giữ nguyên, cạnh loại 2 đổi chiếc). Vậy mỗi nhóm chỉ có đúng hai cách xếp, và hai cách đó đối xứng nhau: nếu nhóm chia thành hai phía nặng $A$ và $B$ thì nhóm góp vào hiệu (chiếc 1 − chiếc 2) đúng $+(A - B)$ hoặc $-(A - B)$. Nếu khi suy ra gặp mâu thuẫn (một em vừa phải ở chiếc này vừa phải ở chiếc kia) thì in $-1$. Đây là phép tô hai màu đồ thị bằng BFS.
- Sau khi gom nhóm, bài toán thành chọn dấu. Gọi $d_i = |A_i - B_i|$ là độ lệch của nhóm $i$, $D = \sum d_i$. Ta cần chọn dấu $\pm$ cho từng $d_i$ để $|\pm d_1 \pm d_2 \pm \ldots|$ nhỏ nhất, tức là chọn một tập các $d_i$ có tổng $T$ lớn nhất mà $T \le \frac{D}{2}$; đáp án là $D - 2T$. Lại là cái túi — nhưng có tới $3 \cdot 10^5$ nhóm và $D$ tới $5 \cdot 10^6$, nên cái túi thường $O(K \cdot D)$ chạy không nổi.
Hình 1. Mỗi nhóm có đúng hai cách xếp (đổi chỗ phía xanh và phía cam), nên chỉ còn lại độ lệch $d$ của nó là quan trọng.
Nút thắt cuối cùng nằm ở một quan sát nhỏ mà rất "đắt": vì $\sum d_i \le \sum w_i \le 5 \cdot 10^6$, số giá trị khác nhau của $d$ không quá khoảng $\sqrt{2 \cdot 5 \cdot 10^6} \approx 3162$ (các số khác nhau $1 + 2 + \ldots + k$ đã bằng $\frac{k(k+1)}{2}$). Gom các nhóm cùng độ lệch lại, tách số lượng theo lũy thừa của $2$, rồi làm cái túi bằng bitset — vậy là xong.
Cách 1Thử mọi cách xếp – O(2^N · M) · 15%
Với $N \le 20$, ta thử cả $2^N$ cách xếp bằng đệ quy quay lui: em thứ $i$ ngồi chiếc 1 hoặc chiếc 2. Khi đã xếp đủ $N$ em, kiểm tra từng ghi chú; nếu thỏa hết thì cập nhật độ chênh $|S - 2s|$ với $s$ là tổng chiếc 1. Không cách nào thỏa thì đáp án giữ nguyên $-1$.
#include <bits/stdc++.h>
using namespace std;
int n, m;
vector<long long> w;
vector<int> t, u, v, boat;
long long tot, best = -1;
void tryAll(int i, long long s) { // s = tong can nang chiec thu nhat
if (i == n) {
for (int j = 0; j < m; j++)
if ((t[j] == 1) != (boat[u[j]] == boat[v[j]])) return;
long long d = llabs(tot - 2 * s);
if (best == -1 || d < best) best = d;
return;
}
boat[i] = 0; tryAll(i + 1, s + w[i]);
boat[i] = 1; tryAll(i + 1, s);
}
int main() {
cin >> n >> m;
w.resize(n); boat.resize(n);
for (auto &x : w) { cin >> x; tot += x; }
t.resize(m); u.resize(m); v.resize(m);
for (int j = 0; j < m; j++) { cin >> t[j] >> u[j] >> v[j]; u[j]--; v[j]--; }
tryAll(0, 0);
cout << best << "\n";
}import sys
sys.setrecursionlimit(10000)
def main():
data = sys.stdin.buffer.read().split()
n = int(data[0]); m = int(data[1])
w = [int(x) for x in data[2:2 + n]]
cons = [(int(data[2 + n + 3 * j]), int(data[3 + n + 3 * j]) - 1, int(data[4 + n + 3 * j]) - 1) for j in range(m)]
tot = sum(w)
boat = [0] * n
best = [-1]
def try_all(i, s): # s = tong can nang chiec thu nhat
if i == n:
for t, u, v in cons:
if (t == 1) != (boat[u] == boat[v]):
return
d = abs(tot - 2 * s)
if best[0] == -1 or d < best[0]:
best[0] = d
return
boat[i] = 0; try_all(i + 1, s + w[i])
boat[i] = 1; try_all(i + 1, s)
try_all(0, 0)
print(best[0])
main()Cách 2Không có ghi chú: cái túi 0/1 – O(N · S) · 35%
Khi $M = 0$, ta chỉ cần biết những tổng $s$ nào "ghép được" từ một nhóm em: $f[s] = 1$ nếu có nhóm em nặng đúng $s$. Ban đầu $f[0] = 1$; thêm em nặng $w$ thì với $s$ giảm dần từ $\frac{S}{2}$ về $w$: nếu $f[s - w] = 1$ thì $f[s] = 1$ (duyệt giảm để mỗi em chỉ được dùng một lần). Cuối cùng lấy $T$ lớn nhất có $f[T] = 1$, đáp án $S - 2T$.
Với ví dụ không có ghi chú (cùng 8 cân nặng, $S = 38$), ta ghép được $19$ nên đáp án là $0$ — chính ghi chú trong đề đã đẩy đáp án lên $2$. Code dưới đây gọi lại Cách 1 khi $N \le 20$ để ăn trọn cả Subtask 1.
#include <bits/stdc++.h>
using namespace std;
int main() {
int n, m; cin >> n >> m;
vector<int> w(n), t(m), u(m), v(m);
long long tot = 0;
for (auto &x : w) { cin >> x; tot += x; }
for (int j = 0; j < m; j++) { cin >> t[j] >> u[j] >> v[j]; u[j]--; v[j]--; }
if (n <= 20) { // Cach 1: thu het 2^n cach xep (bit i = chiec cua em i)
long long best = -1;
for (long long mask = 0; mask < (1LL << n); mask++) {
bool ok = true;
for (int j = 0; j < m && ok; j++)
if ((t[j] == 1) != ((mask >> u[j] & 1) == (mask >> v[j] & 1))) ok = false;
if (!ok) continue;
long long s = 0;
for (int i = 0; i < n; i++) if (mask >> i & 1) s += w[i];
if (best == -1 || llabs(tot - 2 * s) < best) best = llabs(tot - 2 * s);
}
cout << best << "\n";
return 0;
}
int H = tot / 2; // Cach 2 (m = 0): cai tui 0/1 tren can nang
vector<char> f(H + 1, 0); // f[s] = 1 neu chon duoc nhom em co tong s
f[0] = 1;
for (int i = 0; i < n; i++)
for (int s = H; s >= w[i]; s--)
if (f[s - w[i]]) f[s] = 1;
int best = H;
while (!f[best]) best--;
cout << tot - 2 * best << "\n";
}import sys
def main():
data = sys.stdin.buffer.read().split()
n = int(data[0]); m = int(data[1])
w = [int(x) for x in data[2:2 + n]]
tot = sum(w)
if n <= 20: # Cach 1: thu het 2^n cach xep (bit i = chiec cua em i)
cons = [(int(data[2 + n + 3 * j]), int(data[3 + n + 3 * j]) - 1, int(data[4 + n + 3 * j]) - 1) for j in range(m)]
best = -1
for mask in range(1 << n):
ok = True
for t, u, v in cons:
if (t == 1) != ((mask >> u & 1) == (mask >> v & 1)):
ok = False
break
if ok:
s = 0
for i in range(n):
if mask >> i & 1:
s += w[i]
if best == -1 or abs(tot - 2 * s) < best:
best = abs(tot - 2 * s)
print(best)
return
H = tot // 2 # Cach 2 (m = 0): cai tui 0/1 tren can nang
mask = (1 << (H + 1)) - 1
f = 1 # bit s = 1 neu chon duoc nhom em co tong s
for x in w:
f = (f | (f << x)) & mask
best = f.bit_length() - 1
print(tot - 2 * best)
main()f bằng $1$ nếu ghép được tổng $s$. Thêm một em nặng $w$ chỉ là f = f | (f << w), rồi & mask để cắt bỏ các tổng vượt $\frac{S}{2}$. Phép dịch và phép OR trên số lớn chạy trong C nên nhanh hơn vòng lặp Python hàng chục lần.Cách 3Tô hai màu từng nhóm, rồi cái túi trên độ lệch – O(N + M + K · D) · 60%
Như phần mở đầu: duyệt BFS từ một em chưa thăm $s$, cho em đó màu $0$. Đi theo cạnh $(u, v)$ loại $t$: màu của $v$ phải là col[u] ^ (t - 1) (loại 1 giữ màu, loại 2 đổi màu). Gặp đỉnh đã tô mà màu khác yêu cầu ⇒ mâu thuẫn, in $-1$. Trong lúc BFS cộng cân nặng vào side[0] hoặc side[1]; xong một nhóm thì $d = |side_0 - side_1|$. Các em không có ghi chú nào là nhóm một người, $d = w$.
Sau đó chạy đúng cái túi của Cách 2 trên dãy $d_1, d_2, \ldots, d_K$ ($K$ là số nhóm) với $D = \sum d_i$ thay cho $S$. Mô phỏng dưới đây cho em xem phần tô màu: ô xanh là màu 0, ô cam là màu 1.
#include <bits/stdc++.h>
using namespace std;
int main() {
int n, m; cin >> n >> m;
vector<long long> w(n + 1);
for (int i = 1; i <= n; i++) cin >> w[i];
vector<vector<pair<int, int>>> adj(n + 1);
for (int j = 0; j < m; j++) {
int t, u, v; cin >> t >> u >> v;
adj[u].push_back({v, t - 1});
adj[v].push_back({u, t - 1});
}
vector<int> col(n + 1, -1), q, ds;
long long D = 0;
for (int s = 1; s <= n; s++) {
if (col[s] != -1) continue;
long long side[2] = {0, 0};
q.assign(1, s); col[s] = 0;
for (int h = 0; h < (int)q.size(); h++) {
int u = q[h]; side[col[u]] += w[u];
for (auto [v, p] : adj[u]) {
int c = col[u] ^ p;
if (col[v] == -1) { col[v] = c; q.push_back(v); }
else if (col[v] != c) { cout << -1 << "\n"; return 0; }
}
}
int d = llabs(side[0] - side[1]);
ds.push_back(d); D += d;
}
int H = D / 2;
vector<char> f(H + 1, 0); // cai tui 0/1 tren cac do lech d
f[0] = 1;
for (int d : ds)
for (int s = H; s >= d; s--)
if (f[s - d]) f[s] = 1;
int best = H;
while (!f[best]) best--;
cout << D - 2 * best << "\n";
}import sys
from collections import deque
def main():
data = sys.stdin.buffer.read().split()
n = int(data[0]); m = int(data[1])
w = [0] + [int(x) for x in data[2:2 + n]]
adj = [[] for _ in range(n + 1)]
for j in range(m):
t = int(data[2 + n + 3 * j]); u = int(data[3 + n + 3 * j]); v = int(data[4 + n + 3 * j])
adj[u].append((v, t - 1)); adj[v].append((u, t - 1))
col = [-1] * (n + 1)
ds = []
for s in range(1, n + 1):
if col[s] != -1:
continue
side = [0, 0]
col[s] = 0; q = deque([s])
while q:
u = q.popleft(); side[col[u]] += w[u]
for v, p in adj[u]:
c = col[u] ^ p
if col[v] == -1:
col[v] = c; q.append(v)
elif col[v] != c:
print(-1)
return
ds.append(abs(side[0] - side[1]))
D = sum(ds); H = D // 2
mask = (1 << (H + 1)) - 1
f = 1 # cai tui 0/1 tren cac do lech d (bit s)
for d in ds:
f = (f | (f << d)) & mask
best = f.bit_length() - 1
print(D - 2 * best)
main()Cách 4Gom √S giá trị khác nhau, tách lũy thừa 2, bitset – O(N + M + D·√D/64) · 100%
Ở Subtask 4 có thể có $3 \cdot 10^5$ nhóm và $D$ tới $5 \cdot 10^6$: cái túi $O(K \cdot D)$ cần cỡ $10^{12}$ phép, ngay cả bitset (nhanh gấp 64 lần) cũng còn $2 \cdot 10^{10}$. Hai nhận xét cứu ta:
- Có ít giá trị khác nhau. Nếu có $k$ giá trị $d$ khác nhau (và dương) thì $D \ge 1 + 2 + \ldots + k = \frac{k(k+1)}{2}$, nên $k \le \sqrt{2D} \approx 3162$. Đếm
cnt[v]= số nhóm có độ lệch $v$. (Nhóm có $d = 0$ thì bỏ qua — đặt kiểu nào cũng như nhau.) - Tách số lượng theo lũy thừa 2. $c$ món cùng giá trị $v$ (được chọn $0..c$ món) tương đương với các "gói" $1, 2, 4, \ldots, 2^{j}$ và một gói phần dư $r = c - (2^{j+1} - 1)$: mọi số từ $0$ tới $c$ đều viết được thành tổng một số gói, và không tổng nào vượt $c$. Vậy giá trị $v$ chỉ cần khoảng $\log_2 c$ món "gói" $take \cdot v$ chứ không phải $c$ món.
Tổng số gói chỉ cỡ vài nghìn, mỗi gói là một lần dp |= dp << (take * v) trên bitset $2{,}5 \cdot 10^6$ bit (khoảng $4 \cdot 10^4$ phép trên số 64 bit) — tổng cộng cỡ $10^8$ phép, chạy dưới $0{,}3$ giây. Mô phỏng sau cho em xem cách gom, tách gói và các tổng ghép được (ô xanh) sau mỗi gói:
Hai chi tiết nhỏ trong code: chỉ cần duyệt $v \le H = \lfloor D/2 \rfloor$ (một độ lệch lớn hơn $H$ không bao giờ nằm trong tập có tổng $\le H$), và đáp án là $D - 2T$ với $T$ là bit $1$ cao nhất không vượt $H$.
#include <bits/stdc++.h>
using namespace std;
const int HMAX = 2500000; // D/2 <= (tong w)/2 <= 2,5 trieu
bitset<HMAX + 1> dp;
int main() {
ios::sync_with_stdio(false); cin.tie(nullptr);
int n, m; cin >> n >> m;
vector<long long> w(n + 1);
for (int i = 1; i <= n; i++) cin >> w[i];
vector<vector<pair<int, int>>> adj(n + 1);
for (int j = 0; j < m; j++) {
int t, u, v; cin >> t >> u >> v;
adj[u].push_back({v, t - 1}); // 0: cung xuong, 1: khac xuong
adj[v].push_back({u, t - 1});
}
vector<int> col(n + 1, -1), q;
vector<long long> ds; long long D = 0;
for (int s = 1; s <= n; s++) {
if (col[s] != -1) continue;
long long side[2] = {0, 0};
q.assign(1, s); col[s] = 0;
for (int h = 0; h < (int)q.size(); h++) {
int u = q[h]; side[col[u]] += w[u];
for (auto [v, p] : adj[u]) {
int c = col[u] ^ p;
if (col[v] == -1) { col[v] = c; q.push_back(v); }
else if (col[v] != c) { cout << -1 << "\n"; return 0; }
}
}
long long d = llabs(side[0] - side[1]);
ds.push_back(d); D += d;
}
vector<int> cnt(D + 1, 0);
for (long long d : ds) cnt[d]++;
long long H = D / 2;
dp[0] = 1;
for (long long v = 1; v <= H; v++) {
for (long long k = 1, c = cnt[v]; c > 0; k *= 2) {
long long take = min(k, c); // tach c = 1 + 2 + 4 + ... + phan du
dp |= dp << (take * v);
c -= take;
}
}
long long best = H;
while (!dp[best]) best--;
cout << D - 2 * best << "\n";
return 0;
}import sys
def main():
data = sys.stdin.buffer.read().split()
n = int(data[0]); m = int(data[1])
w = [0] * (n + 1)
for i in range(1, n + 1):
w[i] = int(data[1 + i])
# danh sach ke dang mang phang (CSR)
deg = [0] * (n + 2)
T = data[2 + n::3]; U = data[3 + n::3]; V = data[4 + n::3]
eu = list(map(int, U)); ev = list(map(int, V)); et = list(map(int, T))
for j in range(m):
deg[eu[j]] += 1; deg[ev[j]] += 1
st = [0] * (n + 2)
for i in range(1, n + 1):
st[i + 1] = st[i] + deg[i]
pos = st[:]
to = [0] * (2 * m); par = [0] * (2 * m)
for j in range(m):
u = eu[j]; v = ev[j]; p = et[j] - 1
k = pos[u]; to[k] = v; par[k] = p; pos[u] = k + 1
k = pos[v]; to[k] = u; par[k] = p; pos[v] = k + 1
col = [-1] * (n + 1)
ds = []
D = 0
for s in range(1, n + 1):
if col[s] != -1:
continue
side0 = 0; side1 = 0
q = [s]; col[s] = 0; h = 0
while h < len(q):
u = q[h]; h += 1
cu = col[u]
if cu:
side1 += w[u]
else:
side0 += w[u]
for k in range(st[u], st[u + 1]):
v = to[k]; c = cu ^ par[k]
if col[v] == -1:
col[v] = c; q.append(v)
elif col[v] != c:
sys.stdout.write("-1\n")
return
d = side0 - side1
if d < 0:
d = -d
ds.append(d); D += d
H = D // 2
cnt = {}
for d in ds:
if 0 < d <= H:
cnt[d] = cnt.get(d, 0) + 1
mask = (1 << (H + 1)) - 1
dp = 1
for v in sorted(cnt):
c = cnt[v]; k = 1
while c > 0:
take = k if k < c else c # tach c = 1 + 2 + 4 + ... + phan du
dp = (dp | (dp << (take * v))) & mask
c -= take; k *= 2
best = dp.bit_length() - 1
sys.stdout.write(str(D - 2 * best) + "\n")
main()for (k = 1; k <= c; k *= 2) { ...; c -= k; } sẽ bỏ sót phần dư: với $c = 5$ chỉ tạo hai gói $1, 2$ (dừng vì $4 > 2$), mất $2$ món. Lỗi này hiếm khi lộ, vì thường chỉ cần dùng khoảng một nửa số món — nhưng bộ test có những test "khe hở lớn" (ba nhóm rất nặng cùng rất nhiều nhóm độ lệch nhỏ) buộc phải dùng hết các món nhỏ.Bộ test chấm "bắt" những lỗi nào?
Bộ test có 36 test (8 – 8 – 10 – 10 theo subtask). Các test lớn có $N = 3 \cdot 10^5$, $\sum w_i$ tới $5 \cdot 10^6$ và từ khoảng $10^5$ tới $3 \cdot 10^5$ nhóm có độ lệch khác $0$. Để không ai "đoán" được đáp án (với rất nhiều món nhỏ, chia kẹo ngẫu nhiên gần như luôn ra $0$ hoặc $1$), hầu hết các test có cấu trúc: đa số độ lệch là bội của một số $g$, cộng vài em "đặc biệt" được chọn bằng máy sao cho tham lam và các cách đoán đều sai — đáp án vì thế rất đa dạng ($2$, $7$, $22$, $1101$, $50000$, $800001$…). Có các test "khe hở lớn" buộc phải dùng hết các nhóm nhỏ, và test $-1$ (mâu thuẫn nằm sâu trong một nhóm lớn).
| Cách làm | Sub 1 | Sub 2 | Sub 3 | Sub 4 | Điểm |
|---|---|---|---|---|---|
| Cách 1 – thử 2^N cách | 8/8 | TLE | TLE | RE/TLE | 15 |
| Cách 2 – M = 0: cái túi 0/1 | 8/8 | 8/8 | WA | TLE | 35 |
| Cách 3 – tô màu + cái túi trên d | 8/8 | 8/8 | 10/10 | TLE | 60 |
| Cách 4 – gom √S giá trị + bitset (lời giải đầy đủ) | 8/8 | 8/8 | 10/10 | 10/10 | 100 |
| Chưa đủ: bitset từng nhóm, không gom | 8/8 | 8/8 | 10/10 | TLE | 60 |
| Sai: quên phần dư khi tách lũy thừa 2 | 7/8 | 7/8 | 9/10 | 8/10 | 85,12 |
| Sai: không kiểm tra mâu thuẫn | 6/8 | 8/8 | 9/10 | 10/10 | 93,75 |
| Sai: tham lam "d lớn trước, đặt vào chiếc nhẹ" | 6/8 | 2/8 | 2/10 | 2/10 | 29,25 |
| Sai: đoán "D mod 2" (hoặc hiệu nếu một nhóm áp đảo) | 4/8 | 0/8 | 1/10 | 0/10 | 10 |
Để ý: ví dụ trong đề được chọn sao cho tham lam, quên phần dư và quên kiểm tra mâu thuẫn đều vẫn in đúng 2 — đừng tin một lời giải chỉ vì nó qua ví dụ nhé.
Ghi chú của mình: các cách C++ ở trên đã được chấm thử với đúng bộ test của bài (đo thời gian CPU): lời giải chuẩn chạy tối đa khoảng 0,2 giây (giới hạn 1 giây); các ô TLE đều chạy quá 3 giây, ô RE của Cách 1 là tràn ngăn xếp đệ quy. Lời giải chuẩn được đối chiếu với chương trình vét cạn trên 600 test nhỏ, không lệch test nào; bản Python cho kết quả trùng khớp trên mọi test và chỉ tốn khoảng 0,8 giây CPU ngay cả trên CPython.
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 😇
- 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.
- 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
Đăng nhận xét