---
# System prepended metadata

title: THI THỬ HSG TỈNH K9 - 2025 - II

---

# THI THỬ HSG TỈNH K9 - 2025 - II

>[!Note] Thông tin
>Sau đây là lời giải của đề thi thử Kỳ thi Chọn HSG tỉnh Bà Rịa - Vũng Tàu K9 lần II (được chuẩn bị theo **ma trận kiến thức của Sở GD&ĐT tỉnh Bà Rịa - Vũng Tàu**)
>
>**Thời gian:** 20:00 - 22:30 ngày 02/03/2025
>
>**Bạn đọc có thể nộp và chấm bài** [TẠI ĐÂY](https://chuyentinbrvt.online/contest/algi_mockhsg9lan2_2025)
>
>:::info
>Chuẩn bị bởi ==team Logica==, thuộc khuôn khổ dự án [**The Algitect (ALGI Project)**](https://www.facebook.com/algitect.project)
>:::

:::success
**Lưu ý:** Lời giải này chỉ hướng đến việc **AC (tức là đạt trọn điểm)** các bài.
:::

[toc]

## Bài 1: Nỗi sợ của loài rồng (5 điểm)

### Tóm tắt đề bài:

Có $n$ con rồng lần lượt bay đến tấn công lâu đài của Trâm.
- Những con rồng thứ ==$k, k \cdot 2, k \cdot 3, \dots$== sẽ bị kẹp đuôi.
- Những con rồng thứ ==$l, l \cdot 2, l \cdot 3, \dots$== sẽ bị cắt vuốt.

**Yêu cầu:** Đếm số lượng con rồng đã bị Trâm kẹp đuôi hoặc cắt vuốt.

**Dữ liệu bảo đảm:** $1 \le k, l \le k \cdot l, n \le 10^{18}$

**Subtasks:**
- $30\%$ số điểm: $1 \le k, l \le n \le 10^6$.
- $30\%$ số điểm: $k \cdot l \le 10^6$.
- $40\%$ số điểm: Không có ràng buộc gì thêm.

### Mô hình hóa bài toán:

Bài toán đơn giản là ==đếm số lượng số từ $1$ đến $n$ chia hết cho $k$ hoặc $l$==.

>[!Caution] Lưu ý
> Những số chia hết cho ==cả $k$ và $l$== chỉ được xem là $1$ số.

### Lời giải

>[!Tip]
>Ta có công thức sau:
>$$
>cnt_x = \left \lfloor \frac{n}{x} \right \rfloor
>$$
> Với $cnt_x$ là số lượng bội của $x$ từ $1$ đến $n$.

Như vậy, ta có thể tính được số lượng bội của $k$ và $l$ là:
$$
\left \lfloor \frac{n}{k} \right \rfloor + \left \lfloor \frac{n}{l} \right \rfloor
$$

Tuy nhiên, với phép tính trên, những số ==vừa chia hết cho $k$ vừa chia hết cho $l$== đang bị đếm lặp lại $2$ lần và cần trừ đi.

>[!Tip] Nhận xét
>Số nhỏ nhất vừa chia hết cho $k$ và vừa chia hết cho $l$ là ==bội chung nhỏ nhất== của $k$ và $l$
>:::success
>Vì vậy, ==bội== của ==bội chung nhỏ nhất== của $k$ và $l$ là những số chúng ta cần trừ đi

Ta tính được bội chung nhỏ nhất của $k$ và $l$ bằng:
$$
\frac{k \cdot l}{\operatorname{GCD}(k, l)}
$$

Kết quả bài toán là:
$$
\left \lfloor \frac{n}{k} \right \rfloor + \left \lfloor \frac{n}{l} \right \rfloor - \left \lfloor \frac{n}{\frac{k \cdot l}{\operatorname{GCD}(k, l)}} \right \rfloor
$$

**Độ phức tạp thời gian:** $O \left( \log \min(k, l) \right)$.
> Độ phức tạp trên là độ phức tạp để tính $\operatorname{GCD}$ bằng [**thuật toán Euclid**](https://wiki.vnoi.info/algo/algebra/euclid).

:::success
:::spoiler Code
```cpp=1
#include <bits/stdc++.h>
using namespace std;

long long n, k, l;

long long __lcm(long long a, long long b) {
	return (a / __gcd(a, b)) * b;
}

int main() {
	cin >> n >> k >> l;
	cout << (n / k) + (n / l) - (n / __lcm(k, l));

	return 0;
}
```
:::

## Bài 2: Mật mã cổ đại (5 điểm)

### Tóm tắt đề bài:

Cho $q$ truy vấn, mỗi truy vấn gồm một số nguyên $n$.

**Yêu cầu:** In ra các ước nguyên tố của $n$ theo thứ tự giảm dần.

**Dữ liệu bảo đảm:** $1 \le q \le 10^5$ và $1 \le n \le 10^6$.

**Subtasks:**
- $20\%$ số điểm: $1 \le q, n \le 100$.
- $40\%$ số điểm: $1 \le q \le 10^4$.
- $40\%$ số điểm: Không có ràng buộc gì thêm.

### Mô hình hóa bài toán:

Thực chất, bài toán yêu cầu ==phân tích thừa số nguyên tố== của $n$.

### Lời giải:

Định nghĩa $E_i$ là ước nguyên tố ==nhỏ nhất== của $i$.
>VD: $E_2 = 2, E_9 = 3$.

Giả sử đã tính được $E_i$ với $i$ từ $1$ đến $n$, có thể tìm được tất cả các ước số nguyên tố của $n$ bằng cách không ngừng chia $n$ cho $E_n$ đến khi $n = 1$.
>VD: Phân tích thừa số nguyên tố của $n = 40$
> - $E_{40} = 2 \rightarrow n = \frac{40}{2} = 20$
> - $E_{20} = 2 \rightarrow n = \frac{20}{2} = 10$
> - $E_{10} = 2 \rightarrow n = \frac{10}{2} = 5$
> - $E_{5} = 5 \rightarrow n = \frac{5}{5} = 1$
> 
> $n = 40$ có các ước nguyên tố là $2$ và $5$.

>[!Tip]
> Tạm gọi việc thực hiện tính trước mảng $E$ là ==sàng ước nguyên tố==. Đúng như tên gọi của nó, phương pháp này áp dụng tư tưởng của thuật toán ==sàng nguyên tố==.
> :::success
> Cụ thể, ta duyệt $i$ tăng dần từ $2$, nếu $i$ chưa tìm được ước nguyên tố $(E_i = 0)$, ta duyệt qua tất cả các bội của $j$ $\left( i, i \cdot 2, i \cdot 3, \dots \right)$ và đánh dấu $E_j = i$.
> :::
> Vì với mỗi số, ta duyệt qua các bội của nó, do đó thuật toán trên có độ phức tạp:
> $$
> O \left( \frac n1 + \frac n2 + \frac n2 \dots \right) \approx O \left( n \log n \right)
> $$
> Với $n$ là giá trị lớn nhất cần sàng tới.
> :::warning
> Tuy nhiên, trong thực tế, thuật toán trên chạy nhanh hơn rất nhiều vì **ta chỉ duyệt qua bội của những số nguyên tố**.
> :::

**Độ phức tạp thời gian:** $O \left( n \log n \right)$.

:::success
:::spoiler Code
```cpp=1
#include <bits/stdc++.h>
using namespace std;

const int N = 1e6;
int q, n, E[N+1];

void sieve() {
    for (int i = 2; i <= N; ++i) {
        if (!E[i]) {
            for (int j = i; j <= N; j += i) {
                E[j] = i;
            }
        }
    }
}


int main() {
    sieve();
    
    cin >> q;
    while (q--) {
        cin >> n;
        
        while (n > 1) {
            int p = E[n];
            cout << p << " ";
            while (n % p == 0) {
                n /= curr;
            }
        }
        
        cout << "\n";
    }
    
    return 0;
}
```
:::

## Bài 3: Chuyển nhà (5 điểm)

### Tóm tắt đề bài:

Khoa có $n$ món đồ, món đồ thứ $i$ có khối lượng là $A_i$. Cậu cần xếp các món đồ vào thùng có sức chứa tối đa là $k$.

**Yêu cầu:** Đếm số cách để Khoa chọn hai món đồ bỏ vào thùng.

**Dữ liệu bảo đảm:** $1 \le n \le 10^6$ và $1 \le A_i, k \le 10^9$.

**Subtasks:**
- $40\%$ số điểm: $2 \le n \le 10^3$.
- $60\%$ số điểm không có ràng buộc gì thêm.

### Mô hình hóa bài toán

Thực chất, bài toán yêu cầu đếm số cặp ==$i \lt j$== thỏa ==$A_i + A_j \le k$==.

### Lời giải:

Xét trường hợp ta cố định $A_i$, khi đó cần đếm số lượng $j$ thỏa:
- $j \gt i$.
- $A_j \le k - A_i$.

Điều này có thể thực hiện bằng ==tìm kiếm nhị phân== cơ bản.

>[!Caution]
> Có thể, khoảng $A_j$ thỏa mãn chứa cả $A_i$ mà ta đang cố định. Khi đó, cần trừ $A_i$ ra.

**Độ phức tạp thời gian:** $O \left( n \log n \right)$.

:::success
:::spoiler Code
```cpp=1
#include <bits/stdc++.h>

using namespace std;
const int N = 1e6;

ll n, k, ans, A[N+1];

int main() {
    cin >> n >> k;
    for (int i = 1; i <= n; i++){
        cin >> A[i];
    }

    sort(A+1, A+1+n);

    for(int i = 1; i < n; i++){
        if (A[i] >= k) break;
        int pos = upper_bound(A+1, A+1+n, k - a[i]) - A - 1;
        ans += max(0, pos - i);
    }

    cout << ans;

    return 0;
}

```
:::


## Bài 4: Hộp ma thuật (5 điểm)

### Tóm tắt đề bài:

Tí có một chiếc hộp ma thuật và được đưa cho một hằng số $K$. Có $q$ truy vấn, mỗi truy vấn thuộc một trong hai dạng:
- `+ x`: Thêm một viên ngọc có sức mạnh $x$ vào hộp.
- `- x`: Lấy ra một viên ngọc có sức mạnh $x$ (đảm bảo có ít nhất một viên trong hộp).

**Yêu cầu:** Với mỗi truy vấn, tính số cách lấy một số viên ngọc trong hộp để tổng sức mạnh bằng $K$. In ra số cách chia dư cho $998244353$

**Dữ liệu bảo đảm:** $1 \le q \le 10^5$ và $2 \le n \le 10^6$.

**Subtasks:**
- $20\%$ số điểm: $1 \le q \le 20$.
- $40\%$ số điểm: $1 \le q,k \le 200$.
- $40\%$ số điểm: Không có ràng buộc gì thêm.

### Mô hình hóa bài toán:

Với mỗi truy vấn, đếm số cách chọn một tập con trong tập hợp các số để tổng bằng $K$.

Đây là một bài toán ==quy hoạch động cái túi (balo)== điển hình.

### Lời giải:

- **Định nghĩa:** ==$dp_i$== là số cách tạo tổng bằng $i$.
- **Khởi gán:** 
    - ==$dp_0 = 1$==.
    - ==$dp_i = 0$== với mọi $i \not = 0$.
- **Công thức:**
    - Khi thêm một giá trị $x$: ==$dp_i = dp_i + dp_{i-x}$== với $i \ge x$.
    > Với những cách tạo ra tổng $i - x$ ban đầu, có thể thêm giá trị $x$ để tạo tổng $i$.
    - Khi xóa đi một giá trị $x$: ==$dp_i = dp_i - dp_{i-x}$==
    > Với những cách tạo ra tổng $i - x$ ban đầu, nay mất đi một giá trị $x$ để tạo tổng $i$.
- **Kết quả:** ==$dp_K$==.

>[!Caution] Lưu ý:
> - Đối với truy vấn `- x` cần phải duyệt $i$ giảm dần để đảm bảo ==$dp_{i-x}$== đang lưu trạng thái của ==truy vấn trước== chứ không phải ==truy vấn hiện tại==.
> - Ngược lại, đối với truy vấn `+ x` cần phải duyệt $i$ tăng dần để ==$dp_{i-x}$== lưu trạng thái khi đã xét thêm giá trị $x$.

**Độ phức tạp thời gian:** $O \left( q \cdot K \right)$.

:::success
:::spoiler Code
```cpp=1
#include <bits/stdc++.h>
using namespace std;

const int N = 5000;
const long long M = 998244353;
long long q, k, dp[N+1];

int32_t main() {
	dp[0] = 1;

	cin >> q >> k;
	while (q--) {
		char chr; int x;
		cin >> chr >> x;

		if (chr == '+')
			for (int i = k; i >= x; i--)
				(dp[i] += dp[i-x]) %= M;

		if (chr == '-')
			for (int i = x; i <= k; i++)
				(((dp[i] -= dp[i-x]) %= M) += M) %= M;

		cout << dp[k] << "\n";
	}

	return 0;
}
```
:::