# Đề TS10 - BRVT - 2022: 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 2022 - 2023.
>
>**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 ba số nguyên $a,b,k$.
**Yêu cầu:** Đếm số lượng số chia hết cho $k$ trong đoạn $[a, b]$.
**Dữ liệu đảm bảo:** $1 \le k, a, b \le 10^{18}$ và $a \le b$.
**Ràng buộc:**
- $40\%$ số điểm ứng với $1 \le k, a, b \le 32000$;
- $40\%$ số điểm ứng với $1 \le k, a, b \le 10^9$, $0 \le b - a \le 10^6$;
- $20\%$ số điểm ứng với $1 \le k, a, b \le 10^{18}$.
### Subtask 1 + 2
Duyệt vòng lặp $i$ từ $a$ đến $b$ rồi kiểm tra $i$ có chia hết cho $k$ không và tăng kết quả.
**Độ phức tạp thời gian:** $O\left( b-a \right)$.
:::success
:::spoiler Code
```cpp = 1
#include <bits/stdc++.h>
using namespace std;
long long k, a, b, res;
int main() {
cin >> k >> a >> b;
for (int i = a; i <= b; i++) {
if (i % k == 0)
res++;
}
cout << res;
return 0;
}
```
:::
### Subtask 3
>[!Tip]
>Các số chia hết cho $k$ trong khoảng từ $1$ đến $n$ có dạng $k,2k,3k,4k, \dots ,dk$. Trong đó $d$ là số lớn nhất sao cho $d\cdot k \le n$ hay $d \le \frac nk$.
>:::success
>Mà $d$ là một số nguyên nên ==$d=\lfloor \frac nk \rfloor$==.
>:::
Như vậy, ta có thể tính nhanh số lượng số chia hết cho $k$ trong khoảng từ $1$ đến $n$ bằng cách lấy kết quả là: $\lfloor \frac n k \rfloor$.
Vậy để giải quyết bài toán trên ta sẽ lấy kết quả:
$$
\left \lfloor \frac bk \right \rfloor - \left \lfloor \frac{a-1}k \right \rfloor
$$
Là các số chia hết cho $k$ từ $1$ đến $b$ và loại đi các số chia hết cho $k$ từ $1$ đến $(a-1)$.
**Độ phức tạp thời gian:** $O \left( 1 \right)$.
:::success
:::spoiler Code
```cpp = 1
#include <bits/stdc++.h>
using namespace std;
long long k, a, b;
int main() {
cin >> k >> a >> b;
cout << b/k - (a-1)/k;
return 0;
}
```
:::
## Bài 2
### Tóm tắt đề bài
Cho hai số $a$ và $b$ ($a \lt b$), tìm số $x$ không âm nhỏ nhất để $\operatorname{UCLN}\left( a + x, \; b + x\right) = b - a$.
**Yêu cầu:** In ra số $x$ không âm nhỏ nhất tìm được
**Dữ liệu đảm bảo:** $1 \lt a \lt b \le 10^{18}$ và $x \ge 0$.
**Subtasks:**
- $50\%$ số điểm: $1 \lt a \lt b \le 10^6$.
- $50\%$ số điểm: không có ràng buộc gì thêm.
### Subtask 1
Chạy vòng lặp ==từ giá trị $0$== đến khi tìm được kết quả $x$ thỏa mãn điều kiện đề bài.
**Độ phức tạp thời gian:** $O \left( b - a \right)$.
:::success
:::spoiler Code
```cpp = 1
#include <bits/stdc++.h>
using namespace std;
long long a, b;
int main() {
cin >> a >> b;
for (int i = 0; ; i++) {
if (__gcd(a + i, b + i) == b - a) {
cout << i;
break;
}
}
return 0;
}
```
:::
### Subtask 2
Đối với giới hạn lớn, việc dùng vòng lặp để tìm $x$ là bất khả thi.
>[!Tip] Gọi:
>- $u = a - b$
>
>Khi đó: $a \bmod u = b \bmod u$ ($1$).
Đề ra $\operatorname{UCLN}\left( a + x, \; b + x \right) = u$, từ đó suy ra:
- $(a + x) \bmod u = 0$
- $(b + x) \bmod u = 0$
Vì tính chất ($1$), ta chỉ cần đảm bảo một trong hai điều kiện trên. Bài toán trở thành ==tìm số $x$ nhỏ nhất sao cho: $(a + x) \bmod u = 0$==.
Ta có: $(a + x) \bmod u = 0 \rightarrow x = \left(-a\right) \bmod u$.
Mà $x \ge 0 \rightarrow x = \left(- \left( a \bmod u \right ) + u\right) \bmod u = u - \left( a \bmod u \right)$
**Độ phức tạp thời gian:** $O \left( 1\right)$.
:::success
:::spoiler Code
```cpp = 1
#include <bits/stdc++.h>
using namespace std;
long long a, b, u;
int main() {
cin >> a >> b;
u = b - a;
cout << u - (a % u);
return 0;
}
```
:::
## Bài 3
### Tóm tắt đề bài
Cho mảng $a$ gồm $n$ phần tử và một số nguyên $k$.
**Yêu cầu:** Hãy đếm số cặp $(i,j)$ sao cho $a_i + a_j = k$ $(1 \le i \lt j \le n)$.
**Dữ liệu đảm bảo:** $1 \le a_i \le 10^9$, $2 \le n \le 10^5$ và $1 \le k \le 2 \cdot 10^9$.
**Ràng buộc:**
- $40\%$ số điểm tương ứng với $2 \le n \le 10^3 , 1 \le a_i \le 32000$ .
- $40\%$ số điểm tương ứng với $2 \le n \le 10^5 , 1 \le a_i \le 10^6$.
- $20\%$ số điểm không có ràng buộc gì thêm.
### Subtask 1
Ta duyệt qua từng cặp $(i,j)$ trong mảng $a$, kiểm tra điều kiện rồi tăng biến đếm.
**Độ 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=1e3;
int n,k,res;
int a[N+5];
int main() {
cin >> n >> k;
for (int i = 1; i <= n; i++) {
cin >> a[i];
for (int j = 1; j < i; j++) {
if (a[i] + a[j] == k) res++;
}
}
cout << res;
return 0;
}
```
:::
### Subtask 2
>[!Tip] Nhận xét
> Vì các phần từ trong mảng nhỏ hơn $10^6$ nên nếu $k \gt 2.10^6$ thì đáp án là 0.
> :::success
> Vậy ở subtask này ta chỉ cần giải quyết bài toán với ==$k \le 2.10^6$==
> :::
Ta cố định từng phần tử $i$, lúc này ta cần đếm số lượng phần tử $j$ $(1 \le j \lt i)$ sao cho:
$$
a_i + a_j = k \Leftrightarrow a_j = k - a_i
$$
Ta có thể xử lý việc này bằng mảng đếm.
Gọi $d_x$ là số lượng phần tử $x$ đã xuất hiện (tức là ở vị trí bé hơn $i$). Vậy với mỗi phần tử $i$ ta sẽ cộng vào kết quả là $d_{k-a_i}$.
**Độ phức tạp thời gian:** $O(n)$.
:::success
:::spoiler Code
```cpp = 1
#include <bits/stdc++.h>
using namespace std;
const int N=1e5,MAXVAL=1e6;
int n, k;
long long res;
int a[N+5], d[MAXVAL*2+5];
int main() {
cin >> n >> k;
if (k > 2e6){
cout << 0;
return 0;
}
for (int i = 1; i <= n; i++){
cin >> a[i];
if (a[i] <= k) res += d[k-a[i]];
d[a[i]]++;
}
cout << res;
return 0;
}
```
:::
### Subtask 3
Vì sử dụng mảng đếm nên nếu phần tử $a_i$ lớn sẽ dẫn đến tràn mảng. Lúc này ta thay thể mảng đếm bằng cấu trúc dữ liệu `map`.
Độ phức tạp: $O\left(n \log n \right)$.
:::success
:::spoiler Code
``` cpp=1
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5;
int n;
long long res, k;
int a[N+5];
map<long long, int> mp;
int main() {
cin >> n >> k;
for (int i = 1; i <= n; i++){
cin >> a[i];
res += mp[k-a[i]];
mp[a[i]]++;
}
cout << res;
return 0;
}
```
:::
## Bài 4
### Tóm tắt đề bài
Cho mảng $a$ gồm $n$ phần tử hãy tìm dãy con dài nhất của $a$ sao cho các phần tử liền kề trong dãy con thỏa mãn các điều kiện sau:
- $0 \lt |i-j| \le 10$
- $|a_j - a_i|>0$
- $|a_j - a_i|$ là một số chính phương.
**Dữ liệu đảm bảo:** $1 \le n \le 10^5$ và $1 \le a_i \le 10^9$.
**Ràng buộc:**
- $20\%$ số điểm tương ứng với $1 \le n \le 20$.
- $40\%$ số điểm tương ứng với $1 \le n \le 10^3$.
- $40\%$ số điểm không có ràng buộc gì.
### Subtask 1
Ta sử dụng thuật toán quay lui sinh ra tất cả các dãy con của mảng $a$, kiểm tra điều kiện rồi lấy đáp án.
**Độ phức tạp thời gian:** $O(2^n)$.
:::success
:::spoiler Code
```cpp = 1
#include <bits/stdc++.h>
using namespace std;
const int N = 20;
int n, res;
int a[N+5];
vector<int> vct;
bool chinh_phuong(int x) {
int v = sqrt(x);
return v*v == x;
}
void back_track(int x) {
if (x > n) {
res = max(res, (int)vct.size());
}
else {
for (int i=0; i<=1; i++){
if (i==1) {
if (vct.size() > 0) {
if (x-vct.back() > 10) continue;
if (abs(a[x] - a[vct.back()]) == 0) continue;
if (!chinh_phuong(abs(a[x] - a[vct.back()])) ) continue;
}
vct.push_back(x);
back_track(x+1);
vct.pop_back();
}
else back_track(x+1);
}
}
}
int main() {
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
}
back_track(1);
cout<<res;
return 0;
}
```
:::
### Subtask 2
Sử dụng thuật toán quy hoạch động.
- **Định nghĩa:** ==$f_i$== là dãy con dài nhất thỏa mã điều kiện kết thúc tại $i$.
- **Khởi gán:** ==$f_i = 1$== (dãy con luôn có ít nhất 1 phần tử là $i$).
Ta duyệt các cặp $(i,j)$.
- **Công thức:** ==$f_i= \max(f_j+1)$== nếu $(i,j)$ thỏa mãn điều kiện đề bài (thêm $a_i$ vào cuối dãy con kết thúc tại $j$ tạo ra dãy con mới kết thúc tại $i$).
Độ phức tạp: $O\left(n^2\right)$.
:::success
:::spoiler Code
```cpp = 1
#include <bits/stdc++.h>
using namespace std;
const int N = 1e3;
int n, res;
int a[N+5], f[N+5];
bool not_stf(int x) {
int v = sqrt(x);
return (x == 0 || v*v != x);
}
int main() {
cin >> n;
for (int i=1;i<=n;i++) {
cin >> a[i];
f[i] = 1;
for (int j = 1; j < i; j++){
if (i-j>10 || not_stf(abs(a[i]-a[j]))) continue;
f[i] = max(f[i], f[j] + 1);
}
res = max(res, f[i]);
}
cout << res;
return 0;
}
```
:::
### Subtask 3
Kế thừa tư tưởng của subtask 2.
Tận dụng điều kiện $0 \lt |i-j| \le 10$, ta chỉ duyệt $j$ từ $i-10$ đến $i-1$.
Độ phức tạp: $O\left(n \times 10\right)$.
:::success
:::spoiler Code
``` cpp=1
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5;
int n, res;
int a[N+5], f[N+5];
bool not_stf(int x) {
int v = sqrt(x);
return x==0 || v*v!=x;
}
int main() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
f[i] = 1;
for (int j = max(1, i-10); j < i; j++){
if (not_stf(abs(a[i] - a[j]))) continue;
f[i] = max(f[i], f[j]+1);
}
res = max(res, f[i]);
}
cout << res;
return 0;
}
```
:::