# Lời giải đề thi HSG tỉnh lớp 11 tỉnh Hà Tĩnh môn Tin học năm học 2024 - 2025 **Author:** - **Nguyễn Trọng Hoàng** (Chuyên tin khoá 3 - THPT Chuyên Hà Tĩnh) - **Đào Nhật Tân** (Chuyên tin khoá 3 - THPT Chuyên Hà Tĩnh) [Link đề bài](https://www.facebook.com/chtcoder/posts/pfbid02b9DkdPf8KpzLWYztt89jhuP8mUUdyLB4YAndh3WtXiLo2ahAHuUJECXqrhh7bc5El?__cft__[0]=AZWSXlO7iRwqHFfrwPOQH_FTbyuwSWMPSzIQtCXnXdw7dVy0KEGqK5ivXITZch1ML02ceJSZz9CmcNgcP1aPOZSzQ36CuyiABRA7xi8e_nga6QbZw50aa0yT6o9DZx8QS7-ks7g5Gfko9Ls7h0GKnLdsHqUuDW9X6G6dw3pr0P4Y0K_GVJX0A66JZsWIgJbmdSdqI6Mgbm6HZ-Eo5aWwatDF&__tn__=%2CO%2CP-R) [Link chấm bài CHTOJ](https://oj.thptchuyenhatinh.edu.vn/contest/hsg11_20242025) ## Bài 1: Thừa số nguyên tố [Link chấm bài](https://oj.thptchuyenhatinh.edu.vn/problem/hsg11_2425_bai1) ### Với $2 \le n \le 10^4, q = 1$ - Duyệt từ $2$ tới $n$ kiểm tra ước nguyên tố nhỏ nhất của mỗi số. - Do ước nguyên tố là ước nhỏ nhất lớn hơn $1$ nên không cần kiểm tra tính nguyên tố. - Độ phức tạp thuật toán là $O(n \sqrt n)$. <details> <summary>Code C++</summary> ```cpp int cnt = 0; for (int i = 2; i <= n; ++i) { int t = i; for (int j = 2; j \* j <= i; ++j) { if (i % j == 0) { t = j; break; } } cnt += (t == x); } cout << cnt; ``` </details> ### Với $n, q \le 10^3$ - Gọi $d_i$ là ước nguyên tố nhỏ nhất của $i$. - Tìm tất cả $d_i$, sau khi tìm được thì lưu vào mảng phân phối $cnt$: với mỗi $d_i$ tăng $cnt(d_i)$ lên $1$x. - Đáp án của $x_i$ là $$. - Độ phức tạp thuật toán là $O(n \sqrt n)$. <details> <summary>Code C++</summary> ```cpp vector<int> cnt(n + 1); for (int i = 2; i <= n; ++i) { int d = i; for (int j = 2; j \* j <= i; ++j) { if (i % j == 0) { d = j; break; } } cnt[d]++; } while (q--) { int x; cin >> x; cout << (x <= n ? cnt[x] : 0) << \'\n\'; } ``` </details> ### Với $n, q \le 10^6$ - Đặt $c_i$ là số để kiểm tra xem $i$ đã duyệt chưa. - Đặt toàn bộ $c_i = 0$. - Duyệt $i$ từ $1$ đến $n$, mỗi $i$ duyệt tất cả bội $j$ của $i$: - Nếu $c_j = 0$ thì chắc chắn $i$ là ước nhỏ nhất của $j$ nên đặt $c_j = 1$ và tăng $cnt(i)$ lên $1$. - Nếu $c_j = 1$ thì bỏ qua. - Còn lại làm giống với $n, q \le 10^3$. - Độ phức tạp thuật toán là $O(n\ log\ n)$. <details> <summary>Code C++</summary> ```cpp vector<int> c(n + 1), cnt(n + 1); for (int i = 2; i <= n; ++i) { for (int j = i; j <= n; j += i) { if (!c[j]) { cnt[i]++; c[j] = 1; } } } while (q--) { int x; cin >> x; cout << (x <= n ? cnt[x] : 0) << \'\n\'; } ``` </details> ## Bài 2: Dãy tam giác [Link chấm bài](https://oj.thptchuyenhatinh.edu.vn/problem/hsg11_2425_bai2) ### Với $n \le 20$ - Tìm tất cả dãy con liên tiếp và kiểm tra. - Độ phức tạp thuật toán là $O(n^5)$. <details> <summary>Code C++</summary> ```cpp int ans = 0; for (int i = 0; i < n; ++i) { for (int j = i + 2; j < n; ++j) { bool c = 1; for (int x = i; x <= j && c; ++x) { for (int y = x + 1; y <= j && c; ++y) { for (int z = y + 1; z <= j && c; ++z) { if (a[x] + a[y] <= a[z] || a[x] + a[z] <= a[y] || a[y] + a[z] <= a[x]) c = 0; } } } if (c) ans = max(ans, j - i + 1); } } cout << ans; ``` </details> ### Với $n \le 10^3$ - Ta thấy với ba số $x, y, z$ mà $x \le y \le z$, dễ thấy $x + z > y$ và $y + z > x$. Vậy chỉ cần xét điều kiện $x + y > z$. - Như vậy trong một dãy, chỉ cần xét hai số nhỏ nhất $l_1, l_2$ và số lớn nhất $h$; dãy sẽ thỏa mãn nếu $l_1 + l_2 > h$. - Dễ thấy nếu một dãy thỏa mãn, tất cả dãy con của nó cũng thỏa mãn nên ta có thể sử dụng kĩ thuật hai con trỏ. - Độ phức tạp thuật toán là $O(n^2)$. <details> <summary>Code C++</summary> ```cpp bool check(int i, int j) { int l1 = 1e9, l2 = 1e9, h = 0; cout << i << \' \'; for (; i <= j; ++i) { if (a[i] <= l1) { l2 = l1; l1 = a[i]; } else l2 = min(l2, a[i]); h = max(h, a[i]); } return l1 + l2 > h; } int ans = 0, i = 0; for (int j = 0; j < n; ++j) { while (j - i + 1 >= 3 && !check(i, j)) i++; if (j - i + 1 >= 3) ans = max(ans, j - i + 1); } cout << ans; ``` </details> ### Với $n \le 10^5$ - Ta có thể tìm $l_1, l_2$ và $r$ bằng CTDL Multiset. - Độ phức tạp thuật toán là $O(n\ log\ n)$ <details> <summary>Code C++</summary> ```cpp int ans = 0, i = 0; multiset<int> s; for (int j = 0; j < n; ++j) { s.insert(a[j]); while (s.size() >= 3 && \*s.begin() + \*++s.begin() <= \*--s.end()) { s.erase(s.find(a[i])); i++; } if (j - i + 1 >= 3) ans = max(ans, j - i + 1); } cout << ans; ``` </details> ### Với $n \le 10^6$ và $a_1 \le a_2 \le \ldots \le a_n$ - Ta thấy $l_1, l_2$ là hai số đầu và $r$ là số cuối của dãy con nên có thể lấy trực tiếp. - Độ phức tạp thuật toán là $O(n)$. <details> <summary>Code C++</summary> ```cpp int ans = 0, i = 0; for (int j = 0; j < n; ++j) { while (j - i + 1 >= 3 && a[i] + a[i + 1] <= a[j]) i++; if (j - i + 1 >= 3) ans = max(ans, j - i + 1); } cout << ans; ``` </details> ## Bài 3: Dãy ngoặc đúng [Link chấm bài](https://oj.thptchuyenhatinh.edu.vn/problem/hsg11_2425_bai3) ### Với $S \le 10^6$ và chỉ có một kí tự $?$ - Giả sử dấu `(` là $1$ và dấu `)` là $-1$ và $t_i$ là tổng $i$ số đầu nếu xem các ngoặc là các số trên. - Ta thấy trong một dãy đúng độ dài $l$ thì $t_i \ge 0$ với mọi $i$ và $t_l = 0$. - Xét hai dấu `(` và `)` cho dấu `?` và tìm đáp án. - Độ phức tạp thuật toán là $O(n)$. <details> <summary>Code C++</summary> ```cpp int get(int f, char c) { s[f] = c; int d = 0, ans = 0; for (int i = 0; i < s.size(); ++i) { d += (s[i] == '(' ? 1 : -1); ans = max(ans, d); if (d < 0) return -1; } if (d != 0) return -1; return ans; } int f = find(s.begin(), s.end(), '?') - s.begin(); cout << max(get(f, '('), get(f, ')')); ``` </details> ### Với $S \le 20$ - Xét tất cả cách điền cho dấu `?`. - Độ phức tạp thuật toán là $O(2^n \cdot n)$. <details> <summary>Code C++</summary> ```cpp int f(int i, int d) { if (d < 0) return -1; if (i == s.size()) return d == 0 ? 0 : -1; int x = d, p = 0; if (s[i] != ')') { int t = f(i + 1, d + 1); if (t != -1) { p = 1; x = max(x, t); } } if (s[i] != '(') { int t = f(i + 1, d - 1); if (t != -1) { p = 1; x = max(x, t); } } return p ? x : -1; } cout << f(0, 0); ``` </details> ### Với $S \le 10^3$ - Gọi $dp(i, k)$ là độ sâu lớn nhất nếu $t_i = k$. - Đặt $dp(0, 0) = 0$ và $dp(0, k) = -1$ với $k \neq 0$. - Với $i \ge 1$ và $k \le n$ ta có: - Nếu $S_i =$ `)` hoặc `?` và $dp(i - 1, j - 1) \neq -1$ thì $dp(i, k) = max(dp(i - 1, j - 1), k, dp(i, k))$. - Nếu $S_i =$ `(` hoặc `?` và $dp(i - 1, j + 1) \neq -1$ thì $dp(i, k) = max(dp(i - 1, j + 1), dp(i, k))$. - Nếu không có cách điền thì $dp(i, k) = -1$. - Đáp án là $dp(n, 0)$. - Độ phức tạp thuật toán là $O(n^2)$. <details> <summary>Code C++</summary> ```cpp int n = s.size(); s = ' ' + s; vector<vector<int>> dp(n + 1, vector<int> (n + 1, -1)); dp[0][0] = 0; for (int i = 1; i <= n; ++i) { for (int k = 0; k <= n; ++k) { if (s[i] != ')' && k > 0 && dp[i - 1][k - 1] != -1) dp[i][k] = max(dp[i - 1][k - 1], k); if (s[i] != '(' && k < n && dp[i - 1][k + 1] != -1) dp[i][k] = max(dp[i][k], dp[i - 1][k + 1]); } } cout << dp[n][0]; ``` </details>