---
# System prepended metadata

title: 'Đề TS10 - BRVT - 2024: Editorial'

---

# Đề 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;
}

```

:::