# HSG 9 Đà Nẵng
## 2020 - 2021
### Bài 1: Số nguyên tố cân bằng
- **Tóm tắt đề bài**: Cho $N = xx...xyx...xx$ $(1 \le x \le 9, 0 \le y \le 9)$, đếm số lượng số $N$ thỏa $N$ là số nguyên tố.
- **Ý tưởng**: Sinh tất cả các số $N$ độ dài $k$ thỏa $N$ cân bằng và $N$ là số nguyên tố.
### Bài 2: Từ đại diện
- **Tóm tắt**: Cho một dãy xâu $S$ và một xâu $P$, đếm số lần $P$ xuất hiện trong $S$, trong $P$ có các dấu '?' là kí tự bất kì.
- **Ý tưởng**: Duyệt qua các xâu $X$ của $S$, nếu với mỗi kí tự của $X$ bằng mỗi kí tự của $P$ thì cộng 1 vào kết quả, nếu kí tự đang xét là '?' thì luôn cộng 1 vào kết quả.
- **Code tham khảo**:
```python=
# Nhập S
# Nhập P
ans = 0
for X in S:
if length(X) != length(P):
continue
for i = 1 --> length(X):
if X[i] == '?' or X[i] == S[i]:
ans += 1
print(ans)
```
Code full:
```cpp=
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define all(x) x.begin(), x.end()
signed main() {
ios_base::sync_with_stdio(false); cin.tie(0);
vector<string> S;
string s; getline(cin, s);
string cur = "";
for(int i = 0; i < s.size(); ++ i) {
if(s[i] == ' ') {
S.push_back(cur);
cur = "";
}
else cur += s[i];
}
S.push_back(cur);
string P; cin >> P;
int ans = 0;
for(int i = 0; i < S.size(); ++ i) {
if(S[i].size() != P.size()) continue;
string p = P;
for(int j = 0; j < P.size(); ++ j)
if(p[j] == '?')
p[j] = S[i][j];
if(S[i] == p) ++ ans;
}
cout << ans;
}
```
### Bài 3: Diện tích lớn nhất
- **Tóm tắt**: đọc đề <(")
- **Ý tưởng**: Cố định đoạn $MA$, có $AB$ tính $MB$ = $\sqrt{AB^2 - MA^2}$, đáp án $m = max(MA * MB)$
- **Code**:
```python=
ans = 0
for a = 1 --> k:
b2 = k * k - a * a
if b2 là số chính phương:
ans = max(a * sqrt(b2), ans)
print(ans)
```
## 2021 - 2022
### Bài 1 & 2
### Bài 3: Tam giác
- Tóm tắt: Cho $a$, $b$ là 2 số nguyên dương. Đếm số số $c$ sao cho $a$, $b$, $c$ là 3 cạnh của một tam giác.
- $a + b > c > a - b$
- $(a + b) - (a - b) - 1 = 2b - 1$ $(b < a)$
### Bài 4: Bội đặc biệt
- Tóm tắt: Cho số $P$ và $N$, và số $X$ có dạng $99...99$, đếm số số $X$ sao cho $X$ chia hết cho $P$ và $X$ có $N$ chữ số.
- Ví dụ: $P = 7$, $N = 7$ -> chỉ có $999999$ là thỏa mãn tính chất trên.
- **Subtask 1**: $P < 100, N \le 10$
Subtask này ta chỉ cần duyệt trâu qua toàn bộ các giá trị $X$ có thể.
```
X = X * 10 + 9
```
$X_{max} = 10^{10}$ $\Rightarrow$ ko sợ bị tràn số
- **Subtask 2**: $100 \le P \le 10000, N \le 160$
Subtask này ta cần biết 1 tính chất sau:
```
(a + b) % m = (a % m + b % m) % m
```
$X_{max} = 10^{160}$ $\Rightarrow$ ko ổn.
Sau khi tính toán, đáp án của mình là $X$ % $P$
Mình vừa tính, vừa mod luôn.
Áp dụng công thức trên:
```
X = (X * 10 + 9) % P (a = x * 10, b = 9)
= (X * 10 % P + 9) % P
```
Sau khi tính xong, nếu $X = 0$ thì $ans + 1$.
- **Subtask 3**: $10^4 \le P \le 10^6$, $N \le 10^{18}$
- Giả sử ta thử $P = 7$, $N = 20$:
$9$: ko thỏa vì ko chia hết cho 7
$99$: như trên
$999$: như trên
$9999$: như trên
$99999$: như trên
$999999$: chia hết cho 7
.
.
.
- Ta nhận thấy, với test trên, chỉ có các số:
$999999$
$999999999999$
$999999999999999999$
thỏa mãn
- Thử $P = 7,8,9,...$, $N = 18$ để củng cố, khẳng định quy luật là đúng.
$\Rightarrow$ Ta có thể rút ra được quy luật: Gọi $L$ là độ dài của số nhỏ nhất thỏa mãn tính chất của đề
$\Rightarrow$ Đáp án sẽ là $\frac{N}{L}$ vì độ dài của những số sau sẽ tăng thêm $L$
## 2019 - 2020
### Bài 1: Đếm cặp đôi
- Tóm tắt: Có số $x$ ($x \le 10^6$), đếm trong dãy $A$ số cặp $(i, j)$ sao cho $A_i + A_j = x$
- **Cách 1: 2 con trỏ (two - pointers)**
***B1***: Sort lại mảng $A$
***B2***: Cố định 2 biến l, r ở 2 đầu của mảng $A$ ($l = 1, r = n$)
$A_l \le A_{l + 1}$ và $A_{r - 1} \le A_r$
***B3***: Check tổng $A_l + A_r$ so với $x$ $\Rightarrow$ 3 trường hợp có thể xảy ra:
*TH1*: $A_l + A_r < x$ $\Rightarrow$ tăng $l$ lên để tổng đến gần $x$ hơn.
*TH2*: $A_l + A_r > x$ $\Rightarrow$ giảm $r$ xuống.
*TH3*: $A_l + A_r == x$ $\Rightarrow$ $ans += (số lượng số $A_l$) * (số lượng số $A_r$).
- **Cách 2: dùng CTDL map (chỉ áp dụng cho C++)**
Khai báo:
```cpp=
map<int, int> mp; // gán cho mỗi giá trị int một giá trị int tương ứng
mp[3] = 2
mp[1000000000] = -1358374985729
mp[-1000] = 100
```
**Ưu điểm**: lưu bất kì giá trị nào mình muốn ($\le 10^{18}$)
**Nhược điểm**: truy cập giá trị trong mảng $O(1)$, truy cập map $O(log$ $n)$ ($n$ là kích thước hiện tại của map)
***B1***: tạo `map<int, int> cnt` để đếm phân phối
***B2***: với mỗi $A_i$, đếm số lượng $x - A_i$ trong mảng rồi cộng vào `ans`
***B3***: `cnt[A[i]] ++`
### Bài 3: Thừa số nguyên tố
- Tóm tắt: tự đọc đề
- **Solution**: đề bảo gì làm nấy <(")
Cụ thể hơn, ta đếm tổng số mũ của mỗi số $A_i$, rồi bỏ số có tổng số mũ lớn nhất
## 2018 - 2019
### Bài 3: Tìm phần thưởng
Code tham khảo (chưa tối ưu: $O(sqrt(m)) = O(10^8)$):
```cpp=
// nhap m
int ans = 0, max_diff = 0;
for(int a = 1 -> sqrt(m)) {
if(m % a == 0) { // a la uoc cua m
// a = q - x
int b = m / i; // b = q + x
int q = (a + b) / 2; // a + b = q - x + q + x = 2q --> q = (a + b) / 2
int p = b - q + 1;
if(q - p + 1 > max_diff) {
max_diff = q - p + 1;
ans = (p + q) / 2;
}
}
}
cout << ans;
```
Tối ưu: thay vì for từ $1$ đến $\sqrt{m}$ thì ta sẽ làm ngược lại, nếu tìm được ước thỏa mãn thì in ra luôn (vì cách này for từ ước lớn nhất $\Rightarrow$ nhỏ nhất $\Rightarrow$ nếu tìm được đáp án thỏa mãn thì chắc chắn đáp án đó là lớn nhất).
test test test test test test test