# Đề THT B - BRVT - 2020: Editorial
>[!Note] Thông tin
>Sau đây là lời giải của Kỳ thi Tin học trẻ bảng B tỉnh Bà Rịa - Vũng Tàu năm học 2019 - 2020.
>
>**Bạn đọc có thể nộp và chấm bài (test tự sinh)** [TẠI ĐÂY](https://chuyentin-brvt.online/contest/algi_thtb_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: Tam giác vuông (40 điểm)
### Tóm tắt đề bài
Cho số nguyên dương $p$.
**Yêu cầu:** Đếm số lượng bộ ba số nguyên dương $a \le b \le c$ thỏa mãn ==$a + b + c = p$== và là ==độ dài ba cạnh của một tam giác vuông==.
**Dữ liệu đảm bảo:** $p \le 10^6$.
### Lời giải
Các bộ $(a, b, c)$ thỏa mãn bài toán là các ==bộ ba Pytago== thỏa $a + b + c = p$.
Theo [**công thức Euclid**](https://www.wikiwand.com/vi/articles/B%E1%BB%99_ba_s%E1%BB%91_Pythagoras), ta có khái niệm ==bộ ba Pytago **nguyên tố**== là bộ $(a, b, c)$ trong đó:
- $a = m^2 - n^2$
- $b = 2mn$
- $c = m^2 + n^2$
> $m, n$ là các số nguyên tố cùng nhau, nghĩa là $\operatorname{UCLN}(m, n) = 1$, và đúng một trong chúng là số chẵn.
Từ một ==bộ ba Pytago **nguyên tố**==, ta có thể tạo ra một ==bộ ba Pytago== bằng cách nhân một số nguyên dương $k$ vào cả $a, b, c$. Tức là $(ka, kb, kc)$.
Như vậy, ta có thể duyệt qua $m$ và $n$ để thử mọi bộ ba Pytago nguyên tố, sau đó lại kiểm tra xem có một bộ ba Pytago nào có thể được tạo ra thỏa mãn $ka + kb + kc = p$ không.
>[!Important] Cách duyệt tối ưu
> Ta duyệt qua mọi $n$ từ $1$ đến $p$, sau đó ta duyệt $m$ từ $n+1$ (vì $m \gt n$) và dừng vòng lặp khi $m^2 + n^2 \gt p$ hoặc $2mn \gt p$.
>
> Vì hai số $m, n$ phải khác tính chẵn lẻ, trong vòng lặp ta luôn cộng $m$ lên $2$.
**Độ phức tạp thời gian:** $O \left( p \sqrt p \right)$.
> Thực tế, độ phức tạp nhỏ hơn nhiều, nhưng khó tính được chính xác.
:::success
:::spoiler Code
```cpp = 1
#include <bits/stdc++.h>
using namespace std;
long long p, res;
int main() {
cin >> p;
for (long long n = 1; n <= p; n++)
for (long long m = n+1; m*m + n*n <= p && 2*m*n <= p; m += 2) {
if (__gcd(m, n) > 1) continue;
long long a = m*m - n*n;
long long b = 2*m*n;
long long c = m*m + n*n;
if (a + b + c > p) break;
if (p % (a + b + c) == 0) res++;
}
cout << res;
return 0;
}
```
:::
## Bài 2: Sân bay (30 điểm)
### Tóm tắt đề bài
Có $n$ người, người thứ $i$ ở sân bay trong khoảng thời gian từ $l_i$ đến $r_i$.
**Yêu cầu:** Tìm số người lớn nhất trong sân bay ở cùng một thời điểm.
**Dữ liệu đảm bảo:** $n \le 10^4$ và $a_i < b_i < 10^9$.
### Lời giải
Gọi $f_i$ là số người trong sân bay tại thời điểm $i$.
Như vậy, nếu người thứ $i$ ở sân bay từ $l_i$ đến $r_i$, ta tăng giá trị của $f_{l_i}, f_{{l_i}+1}, \dots, f_{r_i}$ lên $1$.
>[!Tip] Tối ưu
> Tăng giá trị của $f_{l_i}, f_{{l_i}+1}, \cdots, f_{r_i}$ nhanh bằng [**mảng hiệu**](https://wiki.vnoi.info/algo/data-structures/prefix-sum-and-difference-array.md).
:::danger
Dễ nhận thấy không thể lưu trữ mảng với $10^9$ phần tử.
Nhưng số lượng giá trị $l_i, r_i$ khác nhau chỉ đạt tối đa $2n$. Do đó ta dùng kĩ thuật [**nén số**](https://viblo.asia/p/roi-rac-hoa-nen-so-38X4E9QA4N2).
:::
**Đáp án**: $\max (f_1, f_2, \cdots, f_{10^9})$
**Độ 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 = 1e4 + 5;
int n, f[2*N], l[N], r[N];
vector <int> vt;
int main(){
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> l[i] >> r[i];
vt.push_back(l[i]);
vt.push_back(r[i]);
}
///Nén số
sort(vt.begin(), vt.end());
vt.erase(unique(vt.begin(), vt.end()), vt.end());
for (int i = 1; i <= n; i++){
l[i] = lower_bound(vt.begin(), vt.end(), l[i]) - vt.begin() + 1;
r[i] = lower_bound(vt.begin(), vt.end(), r[i]) - vt.begin() + 1;
}
///Mảng hiệu
for (int i = 1; i <= n; i++){
f[l[i]]++;
f[r[i] + 1]--;
}
int res = 0;
for (int i = 1; i <= 2*n; i++){
f[i] = f[i-1] + f[i];
res = max(res, f[i]);
}
cout << res;
return 0;
}
```
:::
## Bài 3: Alibaba mua hàng (30 điểm)
### Tóm tắt đề bài
- Cho $n$ loại tiền có mệnh giá là $a_i$ khác nhau.
- Cho $m$ món hàng với giá tiền của món đồ thứ $i$ là $b_i$
- Một món hàng có thể được mua nếu có thể dùng các đồng tiền trong $n$ loại tiền để tạo ra giá trị ==đúng bằng $b_i$== (mỗi đồng tiền chỉ sử dụng một lần với một món hàng).
**Yêu cầu:** in ra số lượng món hàng có thể mua được.
**Dữ liệu đảm bảo:**
- $n \le 100$;
- $m \le 10000$;
- $a_i \le 100$;
- $b_i \le 10000$.
### Lời giải
Áp dụng ==quy hoạch động cái túi==.
- **Định nghĩa:** ==$f_{i, j}$== là khả năng tạo ra giá trị $j$ nếu chỉ xét $i$ đồng tiền đầu tiên.
- ==$f_{i, j} = 1$==: Có thể tạo ra giá trị $j$ với $i$ đồng tiền đầu tiên.
- ==$f_{i, j} = 0$==: Không thể tạo ra giá trị $j$ với $i$ đồng tiền đầu tiên.
- **Khởi gán:** ==$f_{0, 0} = 1$== (có thể tạo được giá trị $0$ bằng cách không lấy đồng nào).
- **Công thức:** ==$f_{i, j} = \max (f_{i-1, j}, f_{i-1, j - A_i})$==.
- $f_{i-1, j} = 1$: Không cần đồng xu thứ $i$ đã có thể tạo được tổng $j$.
- $f_{i-1, j - A_i} = 1$: Đã tạo được tổng $j - A_i$ mà không cần dùng đồng xu thứ $i$, do đó nếu dùng thêm đồng xu thứ $i$ ta được tổng $j$.
**Đáp án:** Món hàng thứ i có thể mua được nếu ==$f_{n, b_i} = 1$==.
:::success
:::spoiler Code
```cpp = 1
#include <bits/stdc++.h>
using namespace std;
const int N = 105, M = 1e4 + 5;
int n, f[N][M], m, a[N], b[M];
int main(){
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
for (int i = 1; i <= m; i++) {
cin >> b[i];
}
f[0][0] = 1;
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= 10000; j++) {
if (j >= a[i]) {
f[i][j] = max(f[i-1][j], f[i-1][j - a[i]]);
}
else {
f[i][j] = f[i-1][j];
}
}
}
int res = 0;
for (int i = 1; i <= m; i++) {
if (f[n][b[i]] == 1) {
res++;
}
}
cout << res;
return 0;
}
```
:::