# Đề TS10 - BRVT - 2015: 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 2015 - 2016.
>
>**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$. Tính giá trị các biểu thức sau:
\begin{align}
A &= \sum_{i=3}^{n} \; \frac{1}{i} \\
B &= \prod_{i=3}^{n} \; \left( \frac{i-2}{i-1} - \sqrt{i} \right) \\
C &= \sum_{i=1}^{2n+1} \; (-1)^{i} \cdot \frac{2^i}{i!}
\end{align}
### Tính biểu thức A
Khởi gán $A = 0$. Duyệt qua mọi $i \in [3,n]$ và cộng ==$\frac{1}{i}$== vào A.
**Độ phức tạp thời gian:** $O \left( n \right)$
### Tính biểu thức B
Khởi gán $B = 1$. Duyệt qua mọi $i \in [3,n]$ và nhân ==$\frac{i-2}{i-1} - \sqrt{i}$== vào B.
**Độ phức tạp thời gian:** $O \left( n \right)$
### Tính biểu thức C
Gọi ==$f(i) = \frac{2^i}{i!}$==. Khi này:
- $f(0) = 1$
- $f(i) = f(i-1) \cdot \frac{2}{i}$.
Khởi gán ==$C = 0$==.
Duyệt qua mọi $i \in [1,n]$, nếu $i$ lẻ thì ==cộng $f(i)$== vào $C$, ngược lại ==trừ $f(i)$== vào $C$.
**Độ phức tạp thời gian:** $O \left( n \right)$
:::danger
**Lưu ý:** Vì bài toán liên quan tới số thực. Vậy nên cần sử dụng kiểu dữ liệu **double** hoặc **long double** để đảm bảo độ chính xác.
:::
:::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
#include<bits/stdc++.h>
using namespace std;
int n;
long double A = 0, B = 1, C = 0, v = 1;
int main() {
cin >> n;
for (int i = 3; i <= n; i++) {
A += (long double)1 / i;
}
for (int i = 3; i <= n; i++) {
B *= (long double)(i - 2) / (i - 1) - sqrtl(i);
}
for (int i = 1; i <= 2*n+1; i++) {
v *= (long double)2 / i;
if (i & 1) {
C += v;
}
else {
C -= v;
}
}
cout << fixed << setprecision(4) << A << '\n' << B << '\n' << C;
return 0;
}
```
:::
## Bài 2:
### Tóm tắt đề bài
Cho 3 số nguyên dương $n, a, b$.
- Tìm bội chung nhỏ nhất của $a$ và $b$.
- Cho biết $n$ có số lượng ước chẵn hay lẻ.
### Tìm bội chung nhỏ nhất
>[!Tip]
> ==Bội chung nhỏ nhất== của $a$ và $b$ là:
> :::info
> $$
> \operatorname{BCNN}(a, b) = \frac{a \cdot b}{\operatorname{UCLN}(a,b)}
> $$
> :::
Sử dụng hàm `gcd()` có sẵn trong thư viện STL của C++, dễ dàng tính được đáp án.
**Độ phức tạp thời gian:** $O \left( \log \min(a, b) \right)$
> Đây là độ phức tạp của thuật toán Euclid để tính UCLN.
### Cho biết số có số lượng ước chẵn hay lẻ
>[!Tip] Nhận xét
> Nếu một số nguyên dương $x \not = 1$ là số chính phương thì có ==số lượng ước lẻ==, ngược lại ==số lượng ước là chẵn==.
>[!Note] Chứng minh
>:::info
>- Một số nguyên dương $x \not = 1$ có thể được viết dưới dạng $p_1^{a_1} \times p_2^{a_2} \dots p_k^{a_k}$, với $p_i$ là ước số nguyên tố của $x$.
>:::
>:::info
>- Số lượng ước của $x$ được tính theo công thức $d(x) = (a_1+1)(a_2+1)\dots(a_k+1)$.
>:::
>:::info
>- Nếu $x$ là số chính phương khác $1$, mọi ước nguyên tố $p_i$ đều có số mũ chẵn, tức mọi $a_i$ chẵn, hay mọi $a_i+1$ đều là số lẻ. Khi này, $d(x)$ lẻ.
>- Ngược lại, khi này tồn tại $p_i$ có số mũ lẻ, tức $a_i$ lẻ, hay $a_i+1$ là số chẵn. Khi này $d(x)$ chẵn.
>:::
Bài toán quy về kiểm tra một số có phải ==số chính phương== hay không.
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$==, nói cách khác, kiểm tra biểu thức sau đây có thỏa không:
$$
\left \lfloor \sqrt x \right \rfloor ^ 2 = x
$$
**Độ phức tạp thời gian:** $O \left( \log n \right)$
> Đây là độ phức tạp của hàm `sqrt()` trong C++ STL.
:::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
#include <bits/stdc++.h>
using namespace std;
long long a, b, n;
long long lcm(long long a, long long b) {
return a * b / __gcd(a, b);
}
bool sqr(long long x) {
long long t = sqrt(x);
return t * t == n;
}
int main() {
cin >> a >> b >> n;
cout << lcm(a, b) << '\n' << sqr(n);
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$. Yêu cầu:
- Liệt kê mọi số nguyên dương chẵn
- Tính giá trị nhỏ nhất của dãy
- Kiểm tra xem dãy có gồm toàn số nguyên tố hay không
- Tìm đoạn con liên tiếp có nhiều phần tử âm nhất
- Tìm số nguyên dương nhỏ nhất không xuất hiện trong dãy
### Liệt kê mọi số nguyên dương chẵn
Duyệt qua mọi $i \in [1,n]$, nếu $a_i$ chia hết cho 2 thì in ra $a_i$
**Độ phức tạp thời gian:** $O \left( n \right)$
### Tính giá trị nhỏ nhất của dãy
Gọi $f_{min}$ là giá trị nhỏ nhất của dãy, khởi gán $f_{min} = \infty$. Duyệt qua mọi $i \in [1,n]$, nếu $a_i \lt f_{min}$ gán $f_{min} = a_i$.
**Độ phức tạp thời gian:** $O \left( n \right)$
### Kiểm tra xem dãy có gồm toàn số nguyên tố hay không
Sử dụng thuật toán **kiểm tra số nguyên tố trong căn bậc 2** hoặc [**sàng nguyên tố Eratosthenes**](https://blog.28tech.com.vn/sang-so-nguyen-to) để giải bài toán.
**Độ phức tạp thời gian:**
- $O \left( \sum_{i=1}^n \sqrt a_i \right)$ nếu kiểm tra trong căn bậc 2.
- $O \left( A \log A \right)$ với $A = \max(a_i)$ nếu kiểm tra bằng sàng nguyên tố.
### Tìm đoạn con liên tiếp có nhiều phần tử âm nhất
Áp dụng ==quy hoạch động cơ bản==.
Gọi $f(i)$ là đoạn con có nhiều phần tử âm nhất kết thúc tại i. Khi này:
$$
f(i) =
\begin{cases}
a_i \lt 0 , \; f(i-1) + 1\\
a_i \ge 0 , \; 0
\end{cases}
$$
**Đáp án:** $\max(f(1),f(2) \dots ,f(n))$.
**Độ phức tạp thời gian:** $O \left( n \right)$
### Tìm số nguyên dương nhỏ nhất không xuất hiện trong dãy
Gọi $mark(x)$ biểu thị $x$ có xuất hiện hay không:
- $mark(x) = 0$ nếu x không tồn tại trong dãy.
- Ngược lại, $mark(x) = 1$.
Duyệt qua mọi $i \in [1,n]$, nếu $a_i > 0$ thì đánh dấu $mark(a_i) = 1$.
Sau đó duyệt qua mọi giá trị $x \in [1,n+1]$, nếu $mark(x) = 0$ thì $x$ là số nguyên dương nhỏ nhất không xuất hiện trong dãy.
**Độ phức tạp thời gian:** $O \left( n \right)$
:::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
#include<bits/stdc++.h>
using namespace std;
int main() {
int n;
vector<int> a;
vector<bool> f, mark;
cin >> n;
a.resize(n);
f.resize(n+5,true);
mark.resize(n+5,false);
for (auto &x : a) {
cin >> x;
}
for (auto &x : a) {
if (x > 0 && !(x & 1)) {
cout << x << ' ';
}
}
cout << '\n' << *min_element(a.begin(),a.end()) << '\n';
f[0] = f[1] = 0;
for (int i = 1, t = sqrt(n); i <= t; i++) {
if (f[i]) {
for (int j = i * i; j <= n; j += i) {
f[j] = 0;
}
}
}
bool ans = 1;
for (auto &x : a) {
if (x < 0 || !f[x]) {
ans = 0;
break;
}
}
cout << (ans ? "YES" : "NO") << '\n';
int max_len = 0, cur_len = 0;
for (auto &x : a) {
if (x >= 0) {
if (cur_len > max_len) max_len = cur_len;
cur_len = 0;
}
else {
cur_len++;
}
}
if (cur_len > max_len) {
max_len = cur_len;
}
cout << max_len << '\n';
for (auto &x : a) {
if (!mark[x] && x > 0) {
mark[x] = 1;
}
}
for (int i = 1; i <= n + 1; i++) {
if (!mark[i]) {
cout << i;
break;
}
}
}
```
:::
## Bài 4: Công ty
### Tóm tắt đề
Cho $n$ đoạn thẳng, đoạn thứ $i$ kéo dài từ $a_i$ đến $b_i$. Hỏi số lượng đoạn thẳng giao nhau tại cùng một điểm nhiều nhất là bao nhiêu.
### Lời giải
Đây là một bài toán **sweep line** cơ bản.
>[!Tip] Nhận xét
> Chỉ điểm **đầu** và **điểm cuối** của mỗi đoạn sẽ ảnh hưởng tới số lượng đoạn thẳng giao nhau tại một điểm.
Gọi $f(x)$ là số lượng đoạn thẳng có chứa điểm $x$. Với mọi $i \in [1,n]$, tăng $f(a_i)$ lên $1$ và giảm $f(b_i+1)$ đi $1$.
> Biểu thị khi đến điểm $a_i$, đoạn thẳng thứ $i$ bắt đầu xuất hiện, còn khi đến $b_i+1$ thì đoạn thẳng thứ $i$ kết thúc.
Sau đó duyệt qua mọi điểm $x_i$ có tồn tại trong mảng $f$ theo thứ tự tăng dần, ta tính được $f(x_i)$ theo công thức sau $f(x_i) = f(x_i) + f(x_{i-1})$.
Tại bất cứ thời điểm nào, $f(x_i)$ cho ta biết ==số lượng đoạn thẳng đi qua điểm $x_i$==.
Đáp án là $\max(f(x_1),f(x_2),\dots,f(x_k))$.
**Độ phức tạp thời gian:** $O \left( n \log n \right)$
:::success
:::spoiler Code
``` cpp
#include<bits/stdc++.h>
using namespace std;
int main() {
int n;
vector<array<int,2>> event;
cin >> n;
for (int i = 1; i <= n; i++) {
int a, b;
cin >> a >> b;
event.push_back({a, 1});
event.push_back({b+1, -1});
}
sort(event.begin(),event.end());
int virus = 0, max_virus = 0;
for (auto &e : event) {
virus += e[1];
max_virus = max(max_virus, virus);
}
cout << max_virus;
return 0;
}
```
:::