# Đề TS10 - BRVT - 2021: Editorial
>[!Note] Thông tin
>Sau đây là lời giải của Kỳ thi Tuyển sinh 10 trường THPT chuyên Lê Quý Đôn tỉnh Bà Rịa - Vũng Tàu năm học 2021 - 2022.
>
>**Bạn đọc có thể nộp và chấm bài (test tự sinh)** [TẠI ĐÂY](https://chuyentin-brvt.online/contest/algi_ts10_15_24).
>
>:::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)
>:::
[toc]
## Bài 1:
### Tóm tắt đề bài
Cho số nguyên dương $n$. Yêu cầu tính:
$$
\begin {flalign}
S &= \frac{2}{3} + \frac{3}{4} + \cdots + \frac{n-1}{n} \\
T &= \frac{1}{3} - \frac{1}{3^2} + \frac{1}{3^3} - \cdots + \frac{1}{3^{2n-1}} - \frac{1}{3^{2n}}
\end {flalign}
$$
**Dữ liệu đảm bảo: $3 \le n \le 100$**.
### **Tính S**
Cho biến $i$ chạy vòng lặp từ $3$ đến $n-1$, cộng ==$\frac{i-1}{i}$== vào kết quả.
**Độ phức tạp thời gian:** $O\left(n\right)$.
### **Tính T**
>[!Tip]Nhận xét:
>$$
>\frac {1}{3^i} = \frac{1}{3^{i-1}} \cdot \frac{1}{3}
>$$
Với mỗi số hạng từ $1$ đến $2n$, duy trì một biến ==$cur = \frac{1}{3^i}$== là giá trị của số hạng đang xét, nếu ==$i$ lẻ==, cộng $cur$ vào đáp án, ngược lại trừ đáp án đi $cur$.
**Độ phức tạp thời gian:** $O\left(n\right)$.
### **Mã nguồn mẫu**
:::info
Độ phức tạp thời gian của cả bài toán là ==tổng độ phức tạp thời gian== của từng yêu cầu.
:::
:::success
:::spoiler Code
```cpp = 1
#include <bits/stdc++.h>
using namespace std;
int main(){
int n;
cin >> n;
double S = 0.0, T = 0.0;
for (int i = 3; i <= n; i++) {
S += 1.0 * (i-1) / i;
}
double cur = 1.0 / 3;
for (int i = 1; i <= 2*n; i++) {
if (i % 2 == 1 {
T += cur;
}
else {
T -= cur;
}
cur /= 3;
}
cout << fixed << setprecision(5) << S << '\n' << T;
return 0;
}
```
:::
## Bài 2:
### Tóm tắt đề bài
Cho số nguyên dương $n$ và $a$, thực hiện các yêu cầu:
- Tìm số nguyên $k$ là số $n$ sau khi đã xóa đi tất cả các số $0$.
- Tìm chữ số tận cùng của $a^n$.
**Dữ liệu đảm bảo:** $2 \le n \le 10^{18}$ và $1 \le a \le 100$.
### **Yêu cầu 1**
Dùng cấu trúc dữ liệu [**vector**](https://blog.28tech.com.vn/stl-vector-trong-c), lần lượt tách và đưa từng chữ số sau cùng của $n$ (tức là $n \bmod 10$) vào vector (ngoại trừ các chữ số $0$). Sau đó, loại bỏ chữ số đó đi bằng cách chia $n$ cho $10$.
### **Yêu cầu 2**
>[!Tip] Nhận xét
>Đáp an của bài toán là $a^n \bmod 10$.
Để tính $a^n \bmod 10$ một cách nhanh chóng, dùng [**lũy thừa nhị phân**](https://wiki.vnoi.info/algo/algebra/binary_exponentation.md) kết hợp với modulo trong lúc tính.
**Độ phức tạp thời gian:** $O\left( \log n \right)$
### **Mã nguồn mẫu**
:::info
Độ phức tạp thời gian của cả bài toán là ==tổng độ phức tạp thời gian== của từng yêu cầu.
:::
:::success
:::spoiler Code
```cpp = 1
#include <bits/stdc++.h>
using namespace std;
long long n;
int a;
int Pow(int a, long long n) {
if (n == 0) {
return 1;
}
int tmp = Pow(a, n/2);
tmp = tmp * tmp;
if (n % 2 == 1) {
tmp = tmp * a;
}
return tmp % 10;
}
int main(){
cin >> n >> a;
//Yêu cầu 1
long long tmp = n;
vector<int> digits;
while (tmp > 0) {
if (tmp % 10 != 0) {
digits.push_back(tmp % 10);
}
tmp /= 10;
}
for (int i = digits.size()-1; i >= 0; i--) {
cout << digits[i];
}
cout << '\n';
//Yêu cầu 2
cout << Pow(a, n);
return 0;
}
```
:::
## Bài 3:
### Tóm tắt đề bài
Cho hai số nguyên dương $n$, $k$ và dãy số nguyên nguyên dương $A_1, A_2, A_3, \dots, A_n$. Thực hiện các yêu cầu:
- In ra các số $A_i$ có dạng $A_i = 5 \cdot h + 2 \left(h \in N \right)$.
- In ra số lượng số nguyên tố trong dãy.
- In ra khoảng cách lớn nhất giữa hai số chính phương (số lượng phần tử nhiều nhất nằm giữa hai số chính phương) trong dãy. In ra $-1$ nếu không tồn tại hai số chính phương.
- Chia dãy số thành hai phần sao cho: Phần thứ nhất có $k$ phần tử, phần thứ hai có $n-k$ phần tử. In ra độ chênh lệch lớn nhất giữa tổng của hai phần.
**Dữ liệu đảm bảo:** $2 \le k \le n \le 10^5$ và $1 \le A_i \le 10^7$.
### **Yêu cầu 1**
>[!Note]Nhận xét
>$$
>\begin {flalign}
>A_i &= 5h + 2 \\
>\Leftrightarrow 5h &= A_i - 2 \\
>\Leftrightarrow h &= \frac {A_i - 2}{5}
>\end {flalign}
>$$
> Mà $h$ nguyên, do đó $A_i - 2$ chia hết cho $5$, hay ==$\left(A_i - 2\right) \bmod 5 = 0$==.
Như vậy, ta duyệt qua dãy và in ra các số thỏa mãn điều kiện trên.
**Độ phức tạp thời gian:** $O \left(n\right)$.
### **Yêu cầu 2**
Vì $A_i \le 10^7$, có thể dùng kĩ thuật [**sàng nguyên tố**](https://wiki.vnoi.info/algo/algebra/prime_sieve.md), sau đó với mỗi số $A_i$ kiểm tra nhanh xem nó có phải số nguyên tố hay không và đếm.
**Độ phức tạp thời gian:** $O(n \log\log n)$.
### **Yêu cầu 3**
>[!Tip]Kiểm tra số chính phương
>:::success
>==Số chính phương== là bình phương của một số nguyên.
>:::
>Ta có thể kiểm tra một số $x$ có phải số chính phương hay không bằng cách lấy ==bình phương phần nguyên của $\sqrt x$== so sánh với ==$x$==.
>Nghĩa là, $x$ chính phương khi và chỉ khi:
>$$
>\left \lfloor \sqrt x \right \rfloor ^ 2 = x
>$$
Để khoảng cách giữa hai số chính phương được chọn là xa nhất, ta chọn số chính phương ==đầu tiên== và ==cuối cùng== của dãy.
**Độ phức tạp thời gian:** $O(n)$.
### **Yêu cầu 4**
>[!Note]Nhận xét:
>Chênh lệch giữa hai phần là lớn nhất khi một phần đạt tổng lớn nhất có thể và phần còn lại phải nhỏ nhất có thể.
>
>Dễ dàng nhận thấy:
>- Nếu $k \ge n-k$, ta nhóm ==$k$ phần tử lớn nhất== chung với nhau và ==$n-k$ phần tử nhỏ nhất== chung với nhau
>- Nếu $k \lt n-k$, ta nhóm ==$n-k$ phần tử lớn nhất== chung với nhau và ==$k$ phần tử nhỏ nhất== chung với nhau
Có thể tìm nhanh tập các phần tử lớn nhất hoặc nhỏ nhát bằng cách sắp xếp lại dãy số.
**Độ phức tạp thời gian:** $O(n \log n)$
### **Mã nguồn mẫu**
:::info
Độ phức tạp thời gian của cả bài toán là ==tổng độ phức tạp thời gian== của từng yêu cầu.
:::
:::success
:::spoiler Code
```cpp = 1
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5;
const int A = 1e7 + 5;
int n, a[N], k, isPrime[A];
bool isSquare(int n) {
int tmp = sqrt(n);
if (tmp * tmp == n) {
return 1;
}
return 0;
}
main(){
cin >> n >> k;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
/// Yêu cầu 1
for (int i = 1; i <= n; i++) {
if ((a[i]-2) % 5 == 0) cout << a[i] << ' ';
}
cout << '\n';
/// Yêu cầu 2
isPrime[1] = 1;
for (int i = 2; i*i <= 1e7; i++) {
if (isPrime[i] == 0) {
for (int j = i*i; j <= 1e7; j += i) {
isPrime[j] = 1;
}
}
}
int cnt = 0;
for (int i = 1; i <= n; i++) {
if (isPrime[a[i]] == 0) {
cnt++;
}
}
cout << cnt << '\n';
/// Yêu cầu 3
int L = -1, R = -1;
for (int i = 1; i <= n; i++) {
if (isSquare(a[i])) {
if (L == -1) L = i;
R = i;
}
}
if (L == R) {
cout << -1 << '\n';
}
else {
cout << R - L - 1 << '\n';
}
/// Yêu cầu 4
sort (a+1, a+1+n);
k = min(k, n-k);
long long S_max = 0, S_min = 0;
for (int i = 1; i <= n; i++) {
if (i > k) {
S_max += a[i];
}
else {
S_min += a[i];
}
}
cout << S_max - S_min;
return 0;
}
```
:::
## Bài 4:
### Tóm tắt đề bài
Cho số nguyên $n$ và dãy số nguyên $A_1, A_2, A_3,\dots, A_n$.
**Yêu cầu:** Chia dãy $A$ thành hai phần sao cho ==tổng của mỗi phần đều là số nguyên tố== và ==chênh lệch giữa hai phần là nhỏ nhất==. In ra $-1$ nếu không có cách chia.
**Dữ liệu đảm bảo:** $2\le n \le 100$ và $1 \le A_i \le 10^4$.
### Lời giải
Áp dụng ==quy hoạch động cái túi==.
**Định nghĩa:** $F_i$ là khả năng tạo ra tổng bằng $i$ từ các phần tử trong dãy:
- $F_i = 1$, có thể tạo được tổng $i$.
- $F_i = 0$, không thể tạo được tổng $i$.
**Khởi gán:** $F_0 = 1$ (không sử dụng phần tử nào).
**Công thức:** Xét mọi phần tử $A_i$ và mọi tổng $j$ bất kỳ. Nếu $j - A_i \ge 0$ và $F_{j-A_i} = 1$ thì $F_j = 1$.
>[!Important] Lưu ý
>Ta tính $F_j$ dựa vào $F_{j-A_i}$ và $j - A_i \lt j$ nên cần duyệt $j$ giảm dần, để khi lấy $F_{j-A_i}$ ta đảm bảo đó là đáp án chưa được cập nhật bởi $A_i$.
>[!Tip]Nhận xét
>Gọi tổng của cả dãy số là $sum$.
>:::success
>Chênh lệch giữa hai phần là nhỏ nhất khi ==tổng của hai phần gần với $\frac {sum}{2}$== nhất.
>:::
>Trong hai phần, sẽ luôn có một phần với tổng $D \le \frac {sum}{2}$ và phần còn lại là $sum - D$ lớn hơn hoặc bằng $\frac {sum}{2}$.
>
> Không mất tính tổng quát, ta chỉ cần tính $D \le \frac {sum}{2}$. Đáp án tối ưu khi ==D lớn nhất==.
Đáp án hợp lệ khi ==$D$ và $sum - D$ là số nguyên tố==, ta có thể kiểm tra điều này bằng kĩ thuật [**sàng nguyên tố**](https://wiki.vnoi.info/algo/algebra/prime_sieve.md).
**Độ phức tạp thời gian:** $O(n \cdot \sum_{i=1}^n {A_i} + n \log \log n)$
> $n \cdot \sum_{i=1}^n {A_i}$: Quy hoạch động.
> $n \log \log n$: Sàng nguyên tố.
### **Mã nguồn mẫu**
:::success
:::spoiler Code
```cpp = 1
#include <bits/stdc++.h>
using namespace std;
const int N = 102 + 5;
const int S = 1e6 + 5;
int n, isPrime[S], a[N], f[S], sum;
int main(){
isPrime[1] = 1;
for (int i = 2; i*i <= 1e6; i++) {
if (isPrime[i] == 0) {
for (int j = i*i; j <= 1e6; j += i) {
isPrime[j] = 1;
}
}
}
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
sum += a[i];
}
f[0] = 1;
for (int i = 1; i <= n; i++) {
for (int j = 1e6; j >= a[i]; j--) {
f[j] = max(f[j], f[j - a[i]]);
}
}
int res = -1;
for (int i = 1; i <= sum/2; i++) {
if (isPrime[i] == 0 && f[i] == 1 && isPrime[sum-i] == 0 && f[sum-i] == 1) {
res = i;
}
}
if (res == -1) {
cout << -1;
}
else {
cout << res << ' ' << sum - res << ' ' << sum - 2*res;
}
return 0;
}
```
:::