# Đề TS10 - BRVT - 2014: 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 2014 - 2015.
>
>**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: (3 điểm)
### Tóm tắt đề bài
Cho hai số nguyên dương $n$ và $x$ $(2 \le n \le 1000,1 \le x \le 9)$. Yêu Cầu:
1. Cho biết chữ số tận cùng của $2^n$.
2. Tính giá trị của các biểu thức sau:
$$
A = \frac{1}{3}+\frac{2}{4}+\dots+\frac{n-1}{n+1}
$$
$$
B = \frac{x}{1!}+\frac{x^2}{2!}+\dots+\frac{x^n}{n!}
$$
### 1. Cho biết chữ số tận cùng của $2^n$.
**Nhận xét**:
- Nếu $n \equiv 0 \pmod{4}$ thì chữ số tận cùng của $2^n$ là 6.
- Nếu $n \equiv 1 \pmod{4}$ thì chữ số tận cùng của $2^n$ là 2.
- Nếu $n \equiv 2 \pmod{4}$ thì chữ số tận cùng của $2^n$ là 4.
- Nếu $n \equiv 3 \pmod{4}$ thì chữ số tận cùng của $2^n$ là 8.
:::info
**Chứng minh:**
- Vì $2^4 \equiv 6 \pmod{10}$ nên $2^{4k} \equiv 6 \pmod{10}$ $(k \in \mathbb{N}^*)$.
- Xét $2^n$ với $n = 4k+x$:
$$
2^n \equiv 2^{4k+x} \equiv 2^{4k} \times 2^x \equiv 6 \times 2^x \pmod{10}.
$$
- Thay lần lượt $x \in \{0 \dots 3\}$ vào biểu thức trên sẽ được điều cần chứng minh.
**Kiến thức sử dụng:**
- Chữ số tận cùng của một số nguyên dương $a$ là $a \bmod 10$.
- Nếu $a \equiv 6 \pmod{10}$ thì $a^k \equiv 6 \pmod{10}$ $(k \in \mathbb{N}^*)$.
- Mọi số nguyên dương đều có thể viết được dưới dạng $4k+x$ $(k \in \mathbb{N}, x \in \{0 \dots 3\})$.
- $a \times b \equiv (a \bmod c) \times (b \bmod c) \pmod{c}$ $(a,b \in \mathbb{N}; c \in \mathbb{N}^*)$.
:::
:::spoiler Code
```cpp
int n,x;
cin >> n >> x;
switch (n % 4) {
case 0: {
cout << 6;
break;
}
case 1: {
cout << 2;
break;
}
case 2: {
cout << 4;
break;
}
case 3: {
cout << 8;
break;
}
}
cout << '\n';
```
:::
### 2. Tính giá trị của biểu thức $A$ và $B$.
**Tính biểu thức $A$**
- Duyệt $i$ từ $1$ đến $n-1$, mỗi lần cộng vào đáp án $\frac{i}{i+2}$.
**Tính biểu thức $B$**
- **Ý tưởng**: duyệt $i$ từ $1$ đến $n$, mỗi lần cộng vào đáp án $\frac{x^i}{i!}$
- **Vấn đề**: Việc tính riêng tử và mẫu của $\frac{x^i}{i!}$ là không khả thi do vấn đề về giới hạn lớn.
- **Giải pháp**: Gọi $a_i$ là giá trị của số thứ $i$. Dễ dàng nhận thấy $a_i = a_{i-1} \times \frac{x}{i}$ $(1 \le i \le n, a_0 = 1)$. Khi này, với mỗi $i$, tính $a_i$ từ $a_{i-1}$ rồi cộng $a_i$ vào đáp án.
:::warning
**Lưu ý**: Đáp án là số thực.
::::
:::spoiler Code
```cpp
double A = 0;
for (int i = 1; i <= n-1; i++) {
A += (double)i/(i+2);
}
double B = 0, t = 1;
for (int i = 1; i <= n; i++) {
t *= (double) x / i;
B += t;
}
cout << fixed << setprecision(6) << A << ' ' << B << '\n';
```
:::
## Bài 2: (5 điểm)
### Tóm tắt đề bài
Cho hai số nguyên dương $n$, $m$ và dãy nguyên dương $a$ gồm $n$ phần tử $(2 \le n \le 10^5,1 \le m \le 10^9)$. Yêu Cầu:
1. Liệt kê các phần tử có giá trị chẵn trong dãy $a$.
2. Cho biết dãy $a$ có tồn tại số nguyên tố nào không?
3. Cho biết có bao nhiêu cặp số $(i,j)$ thỏa mãn $a_i+a_j$ là số lẻ $(1 \le i \lt j \le n)$.
4. Cho biết giá trị của mẫu số sau khi thực hiện giản ước phân thức sau: $$\frac{a_1 \times a_2 \times \dots \times a_n}{m}$$
### 1. Liệt kê các phần tử có giá trị chẵn trong dãy $a$.
**Nhận xét**: Một số $x$ được gọi là chẵn nếu $x \equiv 0 \pmod{2}$.
:::spoiler Code
```cpp
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
for (int i = 1; i <= n; i++) {
if (a[i] % 2 == 0) {
cout << a[i] << ' ';
}
}
cout << '\n';
```
:::
### 2. Cho biết dãy $a$ có tồn tại số nguyên tố nào không?
Đơn giản duyệt qua mọi phần tử trong dãy $a$ và kiểm tra xem có số nguyên tố nào không. Tuy nhiên, vấn đề là làm sao để biết một số có phải số nguyên tố hay không?
**Thuật toán kiểm tra nguyên tố trong $O(\sqrt{n})$.**
**Nhận xét**:
- Mọi số nguyên dương $x$ đều có thể được phân tích dưới dạng $a \times b$ $(1 \le a,b \le n)$.
- Giả sử $a \le b$, khi này chứng minh được $a \le \sqrt{n}$.
:::info
**Chứng minh**:
Giả sử $a \gt \sqrt{n}$. Mà $a \le b$, suy ra $b \gt \sqrt{n}$. Khi này $a \times b \gt \sqrt{n} \times \sqrt{n} = n$ (Vô lý).
:::
Từ nhận xét trên rút ra được thuật toán:
- Nếu $\forall i \in [2, \sqrt{n}],n \not\equiv 0 \pmod{i}$ thì n là số nguyên tố. Ngược lại, n là hợp số.
:::spoiler Code
```cpp
bool check_prime(int x) {
if (x <= 1) return false;
int t = sqrt(x);
for (int i = 2; i <= t; i++) {
if (x % i == 0) {
return false;
}
}
return true;
}
```
:::
**Thuật toán kiểm tra nguyên tố trong $O(1)$, chuẩn bị trong $O(n \log{\log{n}})$ (Sàng nguyên tố eratosthenes).**
các bạn có thể tham khảo tài liệu tại đây:
https://wiki.vnoi.info/algo/algebra/prime_sieve.md.
:::spoiler Code
```cpp
bool isPrime[10005];
void sieve() {
fill(isPrime,isPrime+10005,true);
isPrime[0] = isPrime[1] = false;
int t = sqrt(10000);
for (int i = 2; i <= t; i++) {
if (isPrime[i]) {
for (int j = i * i; j <= 10000; j += i) {
isPrime[j] = false;
}
}
}
}
bool check_prime(int x) {
if (x <= 1) return false;
return isPrime[x];
}
```
:::
Bây giờ đã có 2 thuật toán kiểm tra số nguyên tố, bây giờ việc của ta chỉ cần duyệt qua dãy $a$ rồi thực hiện theo yêu cầu của đề bài.
:::spoiler Code
```cpp
bool prime_exist = false;
for (int i = 1; i <= n; i++) {
if (check_prime(a[i])) {
prime_exist = true;
break;
}
}
cout << (prime_exist ? "YES" : "NO") << '\n';
```
:::
### 3. Cho biết có bao nhiêu cặp số $(i,j)$ thỏa mãn $a_i+a_j$ là số lẻ.
**Nhận xét**: $a + b$ là số lẻ $\Leftrightarrow$ $a$ và $b$ khác tính chẵn lẻ
**Ý Tưởng**:
- Duyệt qua mọi i từ $1$ đến $n$, với mỗi $a_i$, ta đếm số lượng $a_j$ $(j \lt i)$ thõa mãn $a_i + a_j \equiv 1 \pmod{2}$.
- Coi số lẻ là $1$, số chẵn là $0$. $\forall i \in \{1 \dots n\}$, nếu:
- $a_i$ = 0: Đếm số lượng số $a_j = 1$ $(j \lt i)$ xuất hiện trước đó.
- $a_i$ = 1: Đếm số lượng số $a_j = 0$ $(j \lt i)$ xuất hiện trước đó.
Việc này có thể có thể dễ dàng thực hiện bằng một mảng đếm.
:::spoiler Code
```cpp
long long pair_count = 0;
vector<int> cnt(2,0);
for (int i = 1; i <= n; i++) {
int t = a[i] % 2;
if (t == 0) {
pair_count += cnt[1];
}
else {
pair_count += cnt[0];
}
cnt[t]++;
}
cout << pair_count << '\n';
```
:::
### 4. Cho biết giá trị của mẫu số sau khi thực hiện giản ước phân thức.
Nếu ta tính riêng tử và mẫu sẽ không khả thi vì mỗi $a_i$ có thể lên tới $10^4$ và $n$ lên tới $10^5$ nên giá trị của tử tối đa có thể lên tới $(10^4)^{10^5}$, vượt quá giới hạn của kiểu long long trong **C++**. Vậy nên cần tìm một cách tiếp cận tốt hơn.
**Nhận xét**: Giá trị của mẫu nếu tối giản phân số $\frac{a}{b}$ là $\frac{b}{\gcd(a,b)}$ $(a,b \in \mathbb{N}^*)$.
Vậy việc tính mẫu số sau khi tối giản phân số $\frac{a_1 \times a_2 \times \dots \times a_n}{m}$ tương đương với việc tính $\frac{m}{\gcd(m,a_1 \times a_2 \times \dots \times a_n)}$.
:::spoiler Code
```cpp
for (int i = 1; i <= n; i++) {
m /= __gcd(m, a[i]);
}
cout << m << '\n';
```
:::
## Bài 3: (2 điểm)
### Tóm tắt đề bài
Dãy $a$ gồm $n$ phần tử được gọi là dãy đặc biệt nếu $\forall i \in \{1, \dots, n\}, a_i = \sum_{j=1}^i 2 \times j$.
**Ví dụ**: dãy $2,6,12,20$ là dãy đặc biệt, còn dãy $2, 5, 12, 20$ không phải dãy đặc biệt.
**Yêu cầu**: Cho dãy nguyên dương $a$ gồm $n$ phần tử. Hãy cho biết sau khi sắp xếp lại theo thứ tự tăng dần, dãy $a$ có phải dãy đặc biệt không?
### Thuật toán $O(n \log{n})$
Đơn giản chỉ cần sắp xếp lại dãy $a$ tăng dần. Nếu $\forall i \in \{1, \dots, n\}, a_i = \sum_{j=1}^i 2 \times j$ thì $a$ là dãy đặc biệt. Ngược lại, $a$ không phải dãy đặc biệt.
:::spoiler Code
```cpp
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
sort(a+1,a+1+n);
long long sum = 0;
for (int i = 1; i <= n; i++) {
sum += 2 * i;
if (a[i] != sum) {
cout << 0;
return 0;
}
}
for (int i = 1; i <= n; i++) {
cout << a[i] << '\n';
}
```
:::
Độ phức tạp cho việc sắp xếp là $O(n \log{n})$ và việc kiểm tra dãy $a$ có độ phức tạp $O(n)$. Vậy tựu chung thuật toán có độ phức tạp $O(n \log{n})$.
### Thuật toán $O(n)$
**Nhận xét**: Nếu $k$ là số đặc biệt thì $\exists \;i \in \mathbb{N}^*, \; k = i \times (i + 1)$.
:::info
Chứng minh:
Vì k là số đặc biệt nên k có dạng $2 + 4 + \dots + 2 \times i$ $(i \in \mathbb{N}^*)$.
Nhận thấy $2 + 4 + \dots + 2 \times i$ là tổng của một dãy số cách đều không giảm.
Áp dụng công thức tính tổng dãy số cách đều có quy luật:
$$
\text{Tổng} = \frac{(\text{Số đầu} + \text{Số cuối}) \times \text{Số số hạng}}{2}
$$
$$
\text{Số số hạng} = \frac{(\text{Số cuối} - \text{Số đầu})}{\text{Khoảng cách}} + 1
$$
Khi này:
Số số hạng của dãy là $\frac{(2 \times i - 2)}{2} + 1$.
Tổng của dãy là $\frac{(2 + 2 \times i) \times ( \frac{2 \times i - 2}{2} + 1)}{2} = i \times (i + 1)$.
:::
Để tính được $i$ nhanh, ta có thể biến đổi lại như sau:
$$
k = i \times (i + 1) \Rightarrow i^2 + i - k = 0 \; (*)
$$
Đến đây ta sử dụng công thức nghiệm cho phương trình bậc 2:
:::info
Công thức nghiệm cho phương trình bậc 2 có dạng $ax^2 + bx + c = 0 \;(a \not= 0)$:
$$
\Delta = b^2 - 4ac
$$
Trường hợp 1: Nếu $\Delta \lt 0$, phương trình vô nghiệm
Trường hợp 2: Nếu $\Delta \ge 0$, phương trình có 2 nghiệm:
$$
x = \frac{-b \pm \sqrt{\Delta}}{2a}
$$
:::
Ta có:
$$
\Delta = 1 + 4k
$$
Vì $k \in \mathbb{N}^*$ nên $\Delta > 0$, phương trình luôn có nghiệm. Nên:
$$
i = \frac{-1 \pm \sqrt{1+4k}}{2}
$$
Mà $i \ge 0$. Nên:
$$
i = \frac{-1 + \sqrt{1+4k}}{2}
$$
Vậy $\frac{-1 + \sqrt{1+4k}}{2}$ là một số nguyên dương thì k là số đặc biệt. Ngược lại, k không phải số đặc biệt.
**Ý tưởng**:
- Với mỗi $a_i$, tìm số $x$ thõa mãn $a_i = x \times (x + 1)$. Nếu $x \not\in \mathbb{N}^*$ hoặc $\exists \; j < i, \; a_j = x \times (x + 1)$, dãy $a$ không phải dãy đặc biệt. Ngược lại là đánh dấu lại $x$ bằng một mảng đánh dấu.
- Duyệt qua mảng đánh dấu từ $1$ tới $n$, nếu tồn tại số chưa được đánh dấu thì dãy $a$ không phải dãy đặc biệt. Ngược lại dãy $a$ là dãy đặc biệt.
:::spoiler Code
```cpp
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6+5;
int n;
bool mark[N];
main() {
cin >> n;
for (int i = 1; i <= n; i++) {
long long a;
cin >> a;
if (a > 1LL * 1e6 * (1e6 + 1)) {
cout << 0;
return 0;
}
int t = (sqrt(4*a+1) - 1) / 2;
if (1LL * t * (t + 1) != a || mark[t]) {
cout << 0;
return 0;
}
mark[t] = true;
}
for (int i = 1; i <= n; i++) {
if (!mark[i]) {
cout << 0;
return 0;
}
}
for (int i = 1; i <= n; i++) {
cout << 1LL * i * (i + 1) << '\n';
}
}
```
:::