---
# System prepended metadata

title: 'HSG9 - BRVT - 2023: Editorial'

---

# Đề HSG9 - BRVT - 2023: Editorial

## Bài 1: Tìm ước chung lớn nhất (6 điểm)

Cho dãy số nguyên dương $A_1, A_2, \dots, A_n$ $\left( 1 \le n \le 2 \cdot 10^5 \right)$ $\left( 1 \le A_i \le 10^6 \right)$.

Tìm ước chung lớn nhất của hai số sao cho giá trị đó là lớn nhất.

### Brute - force:

==Duyệt tất cả các cặp số của dãy== và tính ước chung lớn nhất, lấy kết quả là giá trị lớn nhất.

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

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

const int N = 2e5;
int n, res, A[N+1];

int main() {
	cin >> n;
	for (int i = 1; i <= n; i++) {
		cin >> A[i];
		for (int j = 1; j <= i-1; j++) {
			res = max(res, __gcd(A[i], A[j]));
        }
	}

	cout << res;

	return 0;
}
```
:::

### Accepted:

Nhận thấy $A_i \le 10^6$, ta có thể thực hiện thuật toán như sau:

Với mỗi số $i$ từ $1 \rightarrow 10^6$, ta ==duyệt qua tất cả các bội của $i$==, nếu gặp một số tồn tại trong dãy $a$, ta tăng $cnt_i$ lên $1$ đơn vị.

Sau khi chạy xong thuật toán trên, ta có ==$cnt_i$ là số lượng số trong dãy $A$ nhận $i$ là ước.==

Đáp án của bài toán là ==$i$ lớn nhất sao cho $cnt_i \ge 2$.==

Độ phức tạp thời gian: $O(10^6 \log 10^6)$.

:::info
:information_source: Lưu ý:

Thuật toán trên được gọi là "sàng ước". Nhiều người nhầm tưởng thuật toán này có độ phức tạp thời gian lớn, tuy nhiên, vì số lượng bội số không ngừng giảm khi số tăng lên, nên thực chất thuật toán này chạy rất nhanh.

Nếu coi $n$ là giới hạn để duyệt $i$, ta có thể tính được độ phức tạp thời gian của thuật toán này như sau:

$$
\sum_{i=1}^n \frac{n}{i} \approx n \log n
$$

:::

:::success
:::spoiler Code

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

const int N = 1e6;
int n, cnt[N+1], ex[N+1];

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

	for (int i = 1; i <= N; i++) {
		for (int j = i; j <= N; j += i) {
			cnt[i] += ex[j];
        }
    }

	for (int i = N; i >= 1; i--) {
		if (cnt[i] >= 2) {
			cout << i;
			break;
		}
    }

	return 0;
}
```

:::

## Bài 2: Đố vui tin học (6 điểm)

Cho dãy số nguyên dương $A_1, A_2, \dots, A_n$ $\left( n \le 10^4 \right)$.

Xác định độ dài dãy con tăng dài nhất của dãy $A$, sao cho số sau phải lớn hơn số trước ít nhất $k$ $\left( k \le 10^3 \right)$.

:::info
:information_source: Định nghĩa:

Dãy con: Một dãy số được hình thành bằng cách xóa một số phần tử (hoặc không xoá phần tử nào) trong dãy ban đầu và không làm thay đổi thứ tự của các phần tử còn lại.
**VD:** $3, 11, 4$ là một dãy con của dãy $3, 2, 11, 4, 5$.

${\sum_{i=a}^b i}$ : Tổng các số từ $a \rightarrow b$.

:::

### Brute - force:

==Quay lui== thử tất cả các dãy con.

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

### Accepted:

==Quy hoạch động cơ bản.==

Định nghĩa $dp_i$ là dãy con dài nhất thỏa mãn yêu cầu đề bài và bắt buộc kết thúc tại $a_i$.

Khởi gán 

Công thức quy hoạch động: 

- $dp_i = 1$ $\left( 1 \le i \le n \right)$: Khởi gán trường hợp dãy có một phần tử.
- $dp_i = \max(dp_i, dp_j + 1)$ $\left( \forall \; j \lt i, \; a_i - a_j \ge k \right)$.

Kết quả: $\max(dp_1, dp_2, \dots, dp_n)$.

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

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

const int N = 1e4;
int n, k, res, A[N+1], dp[N+1];

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

		dp[i] = 1;
		for (int j = 1; j <= i-1; j++) {
			if (A[i] - A[j] >= k) {
				dp[i] = max(dp[i], dp[j] + 1);
            }
        }

		res = max(res, dp[i]);
	}

	cout << res;

	return 0;
}
```
:::

## Bài 3: Trò chơi (8 điểm)

Cho $n$ ô vuông, mỗi ô vuông có giá trị năng lượng $h_i$ $(1 \le n \le 10^5)$.

Nếu đang ở ô vuông thứ $i$, chỉ có thể nhảy đến ô $i+1, i+2, \dots, i+k$ $\left( 1 \le k \le 10^3 \right)$.

Để di chuyển từ ô $i$ đến ô $j$ cần chi phí năng lượng là $\lvert h_i - h_j \rvert$.

Tìm chi phí nhỏ nhất để đi từ ô $1$ đến ô $n$.

### Accepted:

==Quy hoạch động cơ bản.==

Định nghĩa $dp_i$ là chi phí nhỏ nhất để nhảy từ ô $1$ đến ô $i$.

Công thức quy hoạch động: 

- $dp_1 = 0$: Khởi gán.
- $dp_i = max(dp_i, dp_j + \lvert h_i - h_j \rvert)$ $\left( \max(1, i-k) \le j \lt i \right)$

Kết quả: $dp_n$.

Độ phức tạp thời gian: $O(n \cdot k)$.

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

const int N = 1e5;
int n, k, H[N+1], dp[N+1];

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

	for (int i = 2; i <= n; i++) {
		dp[i] = dp[i-1] + abs(H[i] - H[i-1]);
		for (int j = i-2; j >= max(1, i-k); j--) {
			dp[i] = min(dp[i], dp[j] + abs(H[i] - H[j]));
        }
	}

	cout << dp[n];

	return 0;
}
```
:::