---
# System prepended metadata

title: hsg11_2425

---

# 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>