# Đề TS10 - BRVT - 2020: 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 2020 - 2021.
>
>**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
Nhập vào một số nguyên dương $n$.
**Yêu cầu**: Tính giá trị các biểu thức:
$$\begin {flalign}\
A &= \frac{1}{6}+\frac{2}{7}+\dots+\frac{2n-1}{2n + 4}
\\
B &= \frac{1}{7}-\frac{2}{7^2}+\dots+\frac{2n+1}{7^{2n + 1}}
\end {flalign}
$$
**Dữ liệu đảm bảo:** $2 \le n \le 100$.
### **Tính biểu thức A**
- Duyệt $i$ từ $1$ đến $2n-1$, mỗi lần cộng vào đáp án $\frac{i}{i+5}$.
- **Độ phức tạp thời gian**: $O(n)$.
### **Tính biểu thức B**
- Duyệt $i$ từ $1$ đến $2n + 1$, mỗi lần cộng vào đáp nếu $i$ lẻ và trừ vào đáp án nếu $i$ chẵn một lượng là $\frac{i}{7^i}$
- Việc tính riêng tử và mẫu của $\frac{i}{7^i}$ là không khả thi do vấn đề về giới hạn lớn.
- Ta biến đổi $\frac{i}{7^i} = i \times \frac{1}{7^i}$. Gọi biến $p$ là giá trị $\frac{1}{7^i}$. Dễ dàng nhận thấy với mỗi lần chay ta chỉ cần gán lại giá trị cho $p = \frac p 7$ tương ứng với việc số mũ của $7$ tăng lên $1$ theo $i$.
- **Độ phức tạp thời gian**: $O(n)$.
>[!Note]
> Tuy $7^i$ là rất lớn, và lưu trữ bằng kiểu số thức sẽ dẫn đến ==sai số nghiêm trọng==, nhưng thực tế ta xét $\frac{1}{7^i}$, do đó sai số chỉ ở những ==hàng thập phân phía sau==. Vì vậy không ảnh hưởng đến kết quả.
:::danger
**Lưu ý**: Lưu đáp án bằng kiểu **số thực**.
:::
### **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 A = 0;
for (int i = 1; i <= 2 * n - 1; i++) {
A += (double)i/(i + 5);
}
double B = 0, p = 1;
for (int i = 1; i <= 2 * n + 1; i++) {
p = (double) p / 7;
if (i % 2 == 1)
B += ((double) i * p);
else
B -= ((double) i * p);
}
cout << fixed << setprecision(5) << A << '\n' << B << '\n';
return 0;
}
```
:::
## Bài 2:
### Tóm tắt đề bài
Cho các số nguyên dương $a$, $b$, $c$, $k$, $x$, $y$.
**Yêu cầu**:
- Tính biểu thức $A = \frac{a \cdot b \cdot c}{k}$.
- Tính biểu thức $B = ((x + 1) \cdot (x + 2) \cdots (x + y)) ~mod~2020$.
**Dữ liệu đảm bảo:** $1 \le a, b, c, k \le 10^{18}$, $1 \le x \le 10^9$ và $1 \le y \le 10^6$.
### **Tính biểu thức A**
Nếu tính thông thường thì biểu thức $A$ sẽ tràn dữ liệu. Do đó cần xử lý số lớn để tính toán.
Ban đầu ta chuyển đổi giá trị $a$ thành một mảng số nguyên $A_i$ với $A_i$ là giá trị tại vị trí $i$ từ phải sang trái của số $a$.
> Ví dụ: $a = 123$ thì $A_1 = 3$.
Sau đó ta tính thông thường, nhân từng phân tử $A_i$ với $B$ từ $i = 1, 2, 3, \dots, n$ với $n$ là độ dài hiện tại của mảng $A_i$. Sau đó ta nhận lần lượt $A_i$ với $C$ tương tư như nhân với $B$.
Cuối cùng, dời $A_i$ lên $5$ vị trí, tức là $A_{i + 5} = A_i$, để khi chia ta lưu được $6$ số thập phân sau dấu phẩy trong $A_i$ với $i \in [0, 5]$.
>[!Tip] Nhận xét về độ dài số
>Độ dài tối đa của mảng $A$ bé hơn 100 vì độ dài giá trị của tích $10^{18} \times 10^{18} \times 10^{18}$ là $57$ sau đó ta thêm $5$ chữ số hàng thập phân thì tối đa độ dài đạt $62$.
**Độ phức tạp thời gian**: $O(1)$.
### **Yêu cầu 2**
Nếu chạy lần lượt $i$ từ $1$ đến $y$ để tính $(x + i)$ và nhân vào kết quả thì sẽ bị tràn dữ liệu.
Do đó, áp dụng tính chất chia lấy dư để xử lý tránh bị tràn số:
$$
(a \times b) \bmod c = [(a \bmod c ) \times (b \bmod c )] \bmod c
$$
**Độ phức tạp thời gian:** $O\left( y \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
Nguyễn Minh
#include <bits/stdc++.h>
using namespace std;
long long a, b, c, k, X, Y;
long long A[1000];
long long d[1000];
int main() {
cin >> a >> b >> c >> k >> X >> Y;
int B = 1;
for (int i = 1; i <= Y; i++)
B = B * ((X + i) % 2020) % 2020;
int n = 0;
while (a > 0)
{
int p = a % 10;
a /= 10;
n++;
A[n] = p;
}
for (int i = 1; i <= 100; i++)
{
long long phu = A[i] * b + d[i]; // d[i] = tổng nhớ tại vị trí i.
d[i] = 0;
A[i] = phu % 10;
phu /= 10;
int j = i;
while (phu > 0)
{
j++;
int p = phu % 10;
phu /= 10;
d[j] += p;
}
}
for (int i = 1; i <= 100; i++)
{
long long phu = A[i] * c + d[i];
A[i] = phu % 10;
phu /= 10;
int j = i;
while (phu > 0)
{
j++;
int p = phu % 10;
phu /= 10;
d[j] += p;
}
}
for (int i = 100; i >= 1; i--)
if (A[i] != 0)
{
n = i; //Xác định độ dài hiện tại của mảng A[i]
break;
}
for (int i = n + 5; i > 5; i--)
{
A[i] = A[i - 5]; // Dời giá trị lên 5 vị trí
A[i - 5] = 0;
}
long long giatri = 0;
for (int i = n + 5; i >= 0; i--)
{
giatri *= 10;
giatri += A[i];
A[i] = 0;
if (giatri >= k)
{
A[i] = giatri / k;
giatri = giatri % k;
}
}
for (int i = n + 5; i >= 0; i--)
if (A[i] != 0)
{
n = i; // xác định lại độ dài của mảng A[i]
break;
}
// Vì làm tròn đến số thập phân thứ 5 nên ta sẽ kiểm tra số thập phân thứ 6
int q = 0;
if (A[0] >= 5)
q = 1;
for (int i = 1; i <= n; i++)
{
A[i] += q;
q = 0;
if (A[i] == 10)
{
A[i] = 0;
q = 1;
}
if (q == 0)
break;
}
if (q == 1)
{
n++;
A[n] = 1;
}
for (int i = n; i > 5; i--)
cout << A[i];
cout << ".";
for (int i = 5; i >= 1; i--)
cout << A[i];
cout << "\n";
cout << B;
return 0;
}
```
:::
## Bài 3:
### Tóm tắt đề bài
Cho dãy số nguyên gồm $n$ phần tử $a_1, a_2,\dots a_{n-1}, a_n$.
**Yêu cầu**:
- In ra các phần tử của dãy có dạng $7 \times k$ ($k$ là số nguyên).
- Nếu tồn tại hai số nguyên âm nằm kề nhau in ra chữ số $1$, ngược lại in ra chữ số $0$.
- Tìm độ dài lớn nhất của đoạn con liên tiếp có các phần tử là số nguyên tố (nếu không tồn tại dãy có ít nhất $2$ phần tử in ra $0$).
- Tìm giá trị tuyệt đối nhỏ nhất của tổng hai phần tử bất kỳ.
**Dữ liệu đảm bảo:** $n \le 10^5$ và $|a_i| \le 10^7$.
### **Yêu cầu 1**
Một số nguyên dương $x$ có dạng $7k$ khi ==$x \bmod 7 = 0$==.
Do đó, ta duyệt qua mọi phần tử và in ra các phần tử 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**
Duyệt qua mọi cặp số kề nhau và kiểm tra xem hai số có thỏa mãn hay không.
**Độ phức tạp thời gian:** $O \left( n \right)$.
### **Yêu cầu 3**
Sử dụng thuật toán [**sàng nguyên tố**](https://wiki.vnoi.info/algo/algebra/prime_sieve.md) để đánh dấu những giá trị nào là số nguyên tố.
Áp dụng ==quy hoạch động== cơ bản:
- **Định nghĩa:** $dp_i$ là độ dài đoạn con liên tiếp có nhiều phần tử nhất kết thúc ở vị trí $i$.
- **Công thức:**
+ Nếu $a_i$ là số nguyên tố, $dp_i = dp_{i - 1} + 1$.
+ Ngược lại, $dp_i = 0$.
- **Kết quả:** $max(dp_i)$.
**Độ phức tạp thời gian:** $O( n \log \log n)$.
> Độ phức tạp của sàng nguyên tố.
### **Yêu cầu 4**
>[!Tip] Nhận xét
> Ta cần tìm tổng hai số trong dãy có giá trị gần bằng $0$ nhất.
Sắp xếp lại mảng $a$ theo thứ tự không giảm, sau đó áp dụng thuật toán ==hai con trỏ==:
- Con trỏ $i$ bắt đầu từ $1$.
- Con trỏ $j$ bắt đầu từ $n$.
- Tổng đang xét là tổng của $a_i$ và $a_j$.
Nếu tăng $i$ lên thì giá trị của tổng sẽ tăng lên, ngược lại, nếu giảm $j$ xuống thì giá trị của tổng sẽ giảm xuống.
Chính vì tính chất đó, ta có thể di chuyển con trỏ tối ưu để tổng có thể gần bằng $0$ nhất và giá trị tuyệt đối của tổng là bé nhất.
**Độ 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;
int n;
long long a[1000005], dp[100005];
int vis[10000005];
int main() {
cin >> n;
for (int i = 1; i <= n; i++)
cin >> a[i];
for (int i = 1; i <= n; i++)
if (a[i] % 7 == 0)
cout << a[i] << " ";
cout << "\n";
bool kt = false;
for (int i = 2; i <= n; i++)
if (a[i] < 0 && a[i - 1] < 0)
{
kt = true;
break;
}
if (kt == true) cout << 1;
else cout << 0;
cout << "\n";
for (int i = 1; i <= n; i++)
t[i] = 0;
for (int i = 2; i * i <= 1e7; i++)
if (vis[i] == 0)
for (int j = i; j * i <= 1e7; j++)
vis[j * i] = 1;
vis[1] = 1;
vis[0] = 1;
long long ans = 0;
for (int i = 1; i <= n; i++)
if (vis[abs(a[i])] == 0 && a[i] > 0)
{
dp[i] = dp[i - 1] + 1;
ans = max(ans, dp[i]);
}
if (ans <= 1)cout << 0;
else cout << ans;
cout << "\n";
sort(a + 1, a + 1 + n);
ans = 1e18;
int j = n;
for (int i = 1; i <= n; i++)
{
if (i > 1)
ans = min(ans, abs(a[i] + a[i - 1]));
if (j > 0)
ans = min(ans, abs(a[i] + a[j]));
while (abs(a[j]) > abs(a[i]) && j > i)
{
ans = min(ans, abs(a[i] + a[j]));
j--;
}
if (j > 0)
ans = min(ans, abs(a[i] + a[j]));
}
cout << ans;
return 0;
}
```
:::
## Bài 4:
### Tóm tắt đề bài
Cho một dãy $n$ số nguyên dương $a_i$ và một dãy $m$ số nguyên dương $b_i$.
**Yêu cầu**: Tìm số lượng phần tử trong mảng $b_i$ có giá trị bằng tổng của ít nhất $1$ dãy con trong mảng $a_i$.
**Dữ liệu đảm bảo:** $1 \le n , a_i \le 100$ và $1 \le m, b_i \le 10^4$.
### Lời giải
>[!Tip] Nhận xét
> Bài toán yêu cầu kiểm tra có thể tạo được tổng $b_i$ bằng một tập con của $a$ không.
Áp dụng thuật toán ==quy hoạch động cái túi==
- **Định nghĩa:** $dp_{i,j}$ đánh dấu tổng $j$ có thể tạo được với $i$ số đầu tiên không:
- $dp_{i,j} = 1$ nghĩa là tạo được tổng có giá trị bằng $j$.
- $dp_{i,j} = 0$ là không tạo được.
- **Khởi gián:** $dp_{0, 0} = 1$.
- **Công thức:** $dp_{i,j} = 1$ khi $dp_{i-1, j - a_i} = 1$ hoặc $dp_{i-1, j} = 1$.
Duyệt qua $b_i$, nếu $dp_{n,b_i} = 1$ nghĩa là giá trị $s_b$ thỏa yêu cầu đề bài, ta tăng đáp án.
**Độ phức tạp thời gian:** $O(n \times \max(b_i))$.
### **Mã nguồn mẫu**
:::success
:::spoiler Code
```cpp = 1
#include <bits/stdc++.h>
using namespace std;
const int N = 100, M = 1e4;
int dp[N+1][M+1], n, a[N+1];
int main() {
cin >> n;
for (int i = 1; i <= n; i++)
cin >> a[i];
dp[0][0] = 1;
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= 10000; j++) {
dp[i][j] = dp[i-1][j];
if (j >= a[i] && dp[i - 1][j - a[i]] == 1) {
dp[i][j] = 1;
}
}
}
long long ans = 0;
for (int i = 1; i <= m; i++) {
if (dp[n][b[i]] == 1) {
ans++;
}
}
cout << ans;
return 0;
}
```
:::