# Đề TS10 - BRVT - 2024: 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 2024 - 2025.
>
>**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:** Hãy đếm số lượng các ước số dương của $N$.
**Dữ liệu đảm bảo:** $10 \le N \le 10^{12}$.
**Ràng buộc:**
- $50\%$ số điểm tương ứng với $1 \le N \le 10^6$
- $50\%$ số điểm không có ràng buộc gì thêm.
### Subtask 1
Duyệt $i$ từ $1$ đến $n$ và đếm các số $i$ mà ==$n$ chia hết cho $i$==.
> Tức là $n \bmod i = 0$.
**Độ phức tạp thời gian:** $O\left( n \right)$.
:::success
:::spoiler Code
```cpp = 1
#include <bits/stdc++.h>
using namespace std;
long long n, kq;
int main() {
cin >> n;
for (long long i = 1; i <= n ;++i) {
if (n % i == 0) {
kq++;
}
}
cout << kq;
return 0;
}
```
:::
### Subtask 2
>[!Tip] Nhận xét
> Giả sử $n$ có một ước là $x$, $y = \frac n x$ cũng là một ước của $n$.
> :::success
> Quan sát thấy, luôn luôn có ít nhất một trong hai số nhỏ hơn hoặc bằng $\sqrt n$
> :::
Do đó, ta duyệt $i$ từ $1$ đến $\left \lfloor \sqrt n \right \rfloor$, nếu tìm thấy số $i$ mà $n \bmod i = 0$, ngoài đếm ước $i$, ta đếm thêm cả ước $\frac n i$ của $n$.
:::danger
**Lưu ý:** Trong trường hợp $i \times i = n$, số $i$ có thể bị đếm $2$ lần.
:::
**Độ phức tạp thời gian:** $O\left( \sqrt n \right)$.
:::success
:::spoiler Code
```cpp = 1
#include <bits/stdc++.h>
using namespace std;
long long n, kq;
int main() {
cin >> n;
//i <= sqrt(n) tương đương i*i <= n
for (long long i = 1; i*i <= n; ++i) {
if (n % i == 0) {
kq += 2;
if (i*i == n) {
kq--;
}
}
}
cout << kq;
return 0;
}
```
:::
## Bài 2
### Tóm tắt đề bài
Cho một số nguyên dương $N$ có số lượng chữ số không vượt quá $10^5$.
**Yêu cầu:** Hoán vị các chữ số của $N$, sao cho sau khi hoán vị ta thu được một số nguyên dương lớn nhất là bội của số $30$.
**Ràng buộc:**
- $50\%$ số điểm tương ứng với số lượng chữ số của $N$ không vượt quá $10$
- $50\%$ số điểm không có ràng buộc gì thêm.
### Về bội số của 30
Một số chia hết cho $30$ khi và chỉ khỉ số đó thỏa mãn:
- Số đó chia hết cho $3$, nghĩa là ==tổng các chữ số chia hết cho $3$==.
- Số đó chia hết cho $10$, nghĩa là ==có số $0$ ở cuối==.
### Subtask 1
Ta thử ==tất cả các cách hoán vị các chữ số của $N$== và tìm số lớn nhất thỏa mãn tất cả điều kiện ở trên.
Để dễ thao tác, ta sử dụng hàm `next_permutation()` có sẵn trong thư viện STL của C++.
**Độ phức tạp thời gian:** $O \left( n! \right)$
### Subtask 2
Ta sắp xếp lại các chữ số theo thứ tự giảm dần để số đạt giá trị lớn nhất.
Trùng hợp, khi đó số $0$ (nếu có) của $N$ cũng sẽ được dồn về cuối số, giúp ta thỏa mãn điều kiện thứ $2$.
Khi ta sắp xếp lại như vậy, tổng các chữ số không bị thay đổi, nên ta kiểm tra xem tổng này có chia hết cho $3$ hay không.
>[!Tip]
> Để dễ thao tác, ta nên sử dụng kiểu dữ liệu `string` để lưu số $N$.
**Độ phức tạp thời gian:** $O \left( n \log n \right)$ với $n$ là số lượng chữ số của $N$.
:::success
:::spoiler Code
```cpp = 1
#include <bits/stdc++.h>
using namespace std;
int sumDig(string s) {
int ret = 0;
for (char x : s) {
ret += (x - '0');
}
return ret;
}
int main() {
string s;
cin >> s;
sort(all(s), greater<char>());
if (s.back() != '0' || sumDig(s) % 3) cout << -1;
else cout << s;
return 0;
}
```
:::
## Bài 3
### Tóm tắt đề bài
Cho dãy $A$ gồm $N$ số nguyên dương. Bằng cách ghi dãy $A$ lặp lại vô hạn lần ta thu được dãy $B$.
**Yêu cầu:** Cho số nguyên dương $K, P$. Tính tổng $K$ phần tử liên tiếp trong dãy $B$ bắt đầu từ phần tử có chỉ số $P$.
**Dữ liệu đảm bảo:**
- $1 \le N \le 10^5$;
- $1 \le K, P \le 10^9$;
- $1 \le A_i \le 10^3$.
**Ràng buộc:**
- $50\%$ số điểm tương ứng với $1 \le K \le 10^4, 1 \le P \le 10^5$
- $50\%$ số điểm không có ràng buộc gì thêm.
### Subtask 1
Thực hiện mô phỏng theo đề bài, ta duy trì một biến vị trí để di chuyển tăng dần, nếu vị trí lớn hơn $n$, ta cập nhật lại biến về $1$.
>[!Tip] Mẹo
> Sử dụng phép toán $\bmod$ để cập nhật lại vị trí.
**Độ phức tạp thời gian:** $O \left( P \right)$.
:::success
:::spoiler Code
```cpp = 1
#include <bits/stdc++.h>
using namespace std;
long long n, kq, l, r, a[1000009], k ,p;
int main() {
cin >> n >> k >> p;
for (int i = 1; i <= n; ++i) {
cin >> a[i];
}
a[0] = a[n];
for (int i = p; i <= k + p - 1; ++i) {
kq += a[i % n];
}
cout << kq;
return 0;
}
```
:::
### Subtask 2
>[!Tip] Nhận xét
> Dãy $B$ là dãy $A$ lặp đi lặp lại vô tận.
>
> Do đó, tổng của $K$ phần tử liên tiếp mà đề bài yêu cầu ta tính cũng được tạo thành từ nhiều tổng của cả dãy $A$ cộng lại và một ít số bị lẻ ra.
>
> :::success
> Ta chỉ cần duyệt để tính các số bị lẻ ra, còn nhóm tổng của $A$ có thể tính nhanh.
> :::
**Độ 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;
long long n, k, p, sum, A[N+1];
int main() {
cin >> n >> k >> p;
for (int i = 1; i <= n; i++) {
cin >> A[i];
sum += A[i];
}
A[0] = A[n];
p %= n;
if (!p) p = n;
//Số lượng nhóm có tổng = A
long long cycle = k / n;
//Phần bị lẻ ra
long long rem = k % n;
long long res = cycle * sum;
for (int i = p; i <= p + rem - 1; i++) {
res += A[i % n];
}
cout << res;
return 0;
}
```
:::
## Bài 4
### Tóm tắt đề bài
Cho dãy $B$ gồm các số nguyên $B_1, B_2, B_3, \dots, B_N$ được gọi là dãy lõm nếu tồn tại chỉ số $i$ $(1 \lt i \lt N)$ sao cho $B_1 > B_2 > \dots > B_i < B_{i+1} < \dots < B_N$.
**Yêu cầu:** Cho trước dãy $A$ gồm $N$ số nguyên dương $A_1, A_2, \dots, A_N$. Hãy tìm ==dãy con lõm có độ dài lớn nhất==.
**Dữ liệu đảm bảo:** $2 \lt 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 $2 \le N \le 20$.
- $30\%$ số điểm tương ứng với $20 \lt N \le 5000$.
- $50\%$ số điểm không có ràng buộc gì thêm.
### Subtask 1
Sinh tất cả dãy con của $A$, kiểm tra tính hợp lệ, và lấy độ dài dãy con dài nhất.
**Độ phức tạp thời gian:** $O(2^n)$.
### Subtask 2
>[!Tip] Nhận xét
>Một ==dãy con lõm== là hợp bởi một ==dãy con giảm== và một ==dãy con tăng== (mỗi dãy gồm ít nhất hai phần tử).
Áp dụng ==quy hoạch động== cơ bản.
- **Định nghĩa:**
- $F_i$ là dãy con giảm dài nhất kết thúc tại $i$.
- $G_i$ là dãy con tăng dài nhất bắt đầu tại $i$.
- **Khởi gán:** $F_i = G_i = 1 \; \forall \; i$ (dãy con một phần tử).
- **Công thức:**
- $F_i = \max(F_j + 1)$ với $j \lt i$ và $A_j \gt A_i$ (thêm $A_i$ vào một dãy kết thúc ở $A_j$).
- $G_i = \max(F_j + 1)$ với $j \gt i$ và $A_j \gt A_i$ (thêm $A_i$ vào một dãy kết thúc ở $A_j$).
Khi đó, ở mỗi vị trí $i$ từ $1$ đến $n$, nếu $F_i \ge 2$ và $G_i \ge 2$ thì có một dãy con lõm nhận $i$ làm điểm thấp nhất (điểm lõm) có độ dài là:
$$
F_i + G_i - 1
$$
**Độ phức tạp thời gian:** $O(n^2)$.
:::success
:::spoiler Code
```cpp = 1
#include <bits/stdc++.h>
using namespace std;
long long n, a[1000009], F[1000009], G[1000009], kq;
int main() {
cin >> n;
for (int i = 1; i <= n; i++)
cin >> a[i];
for (int i = 1; i <= n; i++) {
F[i] = 1;
for (j = 1; j <= i-1; j++) {
if (a[j] > a[i]) {
F[i] = max(F[i], F[j]+1);
}
}
}
for (int i = n; i >= 1; i--) {
G[i] = 1;
for (j = n; j >= i+1; j--) {
if (a[j] > a[i]) {
G[i] = max(G[i], G[j]+1);
}
}
}
for (int i = 1; i <= n; i++) {
kq = max(kq, F[i] + G[i] - 1);
}
cout << kq;
return 0;
}
```
:::
### Subtask 3
Tư tưởng kế thừa từ subtask trước, tuy nhiên cần cải tính việc tính $F$ và $G$.
Bạn đọc tham khảo bài viết về giải thuật **dãy con tăng dài nhất** nâng cao [**sau đây**](https://viblo.asia/p/bai-toan-lis-nang-cao-va-mot-so-ung-dung-cua-lis-924lJgd65PM).
**Độ phức tạp thời gian:** $O(n \log n)$.
:::success
:::spoiler Code
```cpp = 1
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5;
int n, A[N+1], F[N+1], G[N+1];
vector<int> V;
int findPos(int x) {
int l = 0, r = V.size() - 1, ret = 0;
while (l <= r) {
int mid = (l + r) >> 1;
if (V[mid] <= x) {
ret = mid;
r = mid - 1;
} else
l = mid + 1;
}
return ret;
}
void initLtoR() {
V.clear();
for (int i = 1; i <= n; i++) {
if (V.empty() || V.back() > A[i]) {
V.pb(A[i]);
F[i] = V.size();
} else {
int pos = findPos(A[i]);
V[pos] = A[i];
F[i] = pos + 1;
}
}
}
void initRtoL() {
V.clear();
for (int i = n; i >= 1; i--) {
if (V.empty() || V.back() > A[i]) {
V.pb(A[i]);
G[i] = V.size();
} else {
int pos = findPos(A[i]);
V[pos] = A[i];
G[i] = pos + 1;
}
}
}
int main() {
cin >> n;
for (int i = 1; i <= n; i++)
cin >> A[i];
initLtoR();
initRtoL();
int res = 0;
for (int i = 1; i <= n; i++) {
if (F[i] >= 2 && G[i] >= 2) {
res = max(res, F[i] + G[i] - 1);
}
}
cout << res;
return 0;
}
```
:::