# Đề TS10 - BRVT - 2022: 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 2022 - 2023. > >**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 ba số nguyên $a,b,k$. **Yêu cầu:** Đếm số lượng số chia hết cho $k$ trong đoạn $[a, b]$. **Dữ liệu đảm bảo:** $1 \le k, a, b \le 10^{18}$ và $a \le b$. **Ràng buộc:** - $40\%$ số điểm ứng với $1 \le k, a, b \le 32000$; - $40\%$ số điểm ứng với $1 \le k, a, b \le 10^9$, $0 \le b - a \le 10^6$; - $20\%$ số điểm ứng với $1 \le k, a, b \le 10^{18}$. ### Subtask 1 + 2 Duyệt vòng lặp $i$ từ $a$ đến $b$ rồi kiểm tra $i$ có chia hết cho $k$ không và tăng kết quả. **Độ phức tạp thời gian:** $O\left( b-a \right)$. :::success :::spoiler Code ```cpp = 1 #include <bits/stdc++.h> using namespace std; long long k, a, b, res; int main() { cin >> k >> a >> b; for (int i = a; i <= b; i++) { if (i % k == 0) res++; } cout << res; return 0; } ``` ::: ### Subtask 3 >[!Tip] >Các số chia hết cho $k$ trong khoảng từ $1$ đến $n$ có dạng $k,2k,3k,4k, \dots ,dk$. Trong đó $d$ là số lớn nhất sao cho $d\cdot k \le n$ hay $d \le \frac nk$. >:::success >Mà $d$ là một số nguyên nên ==$d=\lfloor \frac nk \rfloor$==. >::: Như vậy, ta có thể tính nhanh số lượng số chia hết cho $k$ trong khoảng từ $1$ đến $n$ bằng cách lấy kết quả là: $\lfloor \frac n k \rfloor$. Vậy để giải quyết bài toán trên ta sẽ lấy kết quả: $$ \left \lfloor \frac bk \right \rfloor - \left \lfloor \frac{a-1}k \right \rfloor $$ Là các số chia hết cho $k$ từ $1$ đến $b$ và loại đi các số chia hết cho $k$ từ $1$ đến $(a-1)$. **Độ phức tạp thời gian:** $O \left( 1 \right)$. :::success :::spoiler Code ```cpp = 1 #include <bits/stdc++.h> using namespace std; long long k, a, b; int main() { cin >> k >> a >> b; cout << b/k - (a-1)/k; return 0; } ``` ::: ## Bài 2 ### Tóm tắt đề bài Cho hai số $a$ và $b$ ($a \lt b$), tìm số $x$ không âm nhỏ nhất để $\operatorname{UCLN}\left( a + x, \; b + x\right) = b - a$. **Yêu cầu:** In ra số $x$ không âm nhỏ nhất tìm được **Dữ liệu đảm bảo:** $1 \lt a \lt b \le 10^{18}$ và $x \ge 0$. **Subtasks:** - $50\%$ số điểm: $1 \lt a \lt b \le 10^6$. - $50\%$ số điểm: không có ràng buộc gì thêm. ### Subtask 1 Chạy vòng lặp ==từ giá trị $0$== đến khi tìm được kết quả $x$ thỏa mãn điều kiện đề bài. **Độ phức tạp thời gian:** $O \left( b - a \right)$. :::success :::spoiler Code ```cpp = 1 #include <bits/stdc++.h> using namespace std; long long a, b; int main() { cin >> a >> b; for (int i = 0; ; i++) { if (__gcd(a + i, b + i) == b - a) { cout << i; break; } } return 0; } ``` ::: ### Subtask 2 Đối với giới hạn lớn, việc dùng vòng lặp để tìm $x$ là bất khả thi. >[!Tip] Gọi: >- $u = a - b$ > >Khi đó: $a \bmod u = b \bmod u$ ($1$). Đề ra $\operatorname{UCLN}\left( a + x, \; b + x \right) = u$, từ đó suy ra: - $(a + x) \bmod u = 0$ - $(b + x) \bmod u = 0$ Vì tính chất ($1$), ta chỉ cần đảm bảo một trong hai điều kiện trên. Bài toán trở thành ==tìm số $x$ nhỏ nhất sao cho: $(a + x) \bmod u = 0$==. Ta có: $(a + x) \bmod u = 0 \rightarrow x = \left(-a\right) \bmod u$. Mà $x \ge 0 \rightarrow x = \left(- \left( a \bmod u \right ) + u\right) \bmod u = u - \left( a \bmod u \right)$ **Độ phức tạp thời gian:** $O \left( 1\right)$. :::success :::spoiler Code ```cpp = 1 #include <bits/stdc++.h> using namespace std; long long a, b, u; int main() { cin >> a >> b; u = b - a; cout << u - (a % u); return 0; } ``` ::: ## Bài 3 ### Tóm tắt đề bài Cho mảng $a$ gồm $n$ phần tử và một số nguyên $k$. **Yêu cầu:** Hãy đếm số cặp $(i,j)$ sao cho $a_i + a_j = k$ $(1 \le i \lt j \le n)$. **Dữ liệu đảm bảo:** $1 \le a_i \le 10^9$, $2 \le n \le 10^5$ và $1 \le k \le 2 \cdot 10^9$. **Ràng buộc:** - $40\%$ số điểm tương ứng với $2 \le n \le 10^3 , 1 \le a_i \le 32000$ . - $40\%$ số điểm tương ứng với $2 \le n \le 10^5 , 1 \le a_i \le 10^6$. - $20\%$ số điểm không có ràng buộc gì thêm. ### Subtask 1 Ta duyệt qua từng cặp $(i,j)$ trong mảng $a$, kiểm tra điều kiện rồi tăng biến đếm. **Độ phức tạp thời gian:** $O \left( n^2 \right)$. :::success :::spoiler Code ```cpp = 1 #include <bits/stdc++.h> using namespace std; const int N=1e3; int n,k,res; int a[N+5]; int main() { cin >> n >> k; for (int i = 1; i <= n; i++) { cin >> a[i]; for (int j = 1; j < i; j++) { if (a[i] + a[j] == k) res++; } } cout << res; return 0; } ``` ::: ### Subtask 2 >[!Tip] Nhận xét > Vì các phần từ trong mảng nhỏ hơn $10^6$ nên nếu $k \gt 2.10^6$ thì đáp án là 0. > :::success > Vậy ở subtask này ta chỉ cần giải quyết bài toán với ==$k \le 2.10^6$== > ::: Ta cố định từng phần tử $i$, lúc này ta cần đếm số lượng phần tử $j$ $(1 \le j \lt i)$ sao cho: $$ a_i + a_j = k \Leftrightarrow a_j = k - a_i $$ Ta có thể xử lý việc này bằng mảng đếm. Gọi $d_x$ là số lượng phần tử $x$ đã xuất hiện (tức là ở vị trí bé hơn $i$). Vậy với mỗi phần tử $i$ ta sẽ cộng vào kết quả là $d_{k-a_i}$. **Độ 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,MAXVAL=1e6; int n, k; long long res; int a[N+5], d[MAXVAL*2+5]; int main() { cin >> n >> k; if (k > 2e6){ cout << 0; return 0; } for (int i = 1; i <= n; i++){ cin >> a[i]; if (a[i] <= k) res += d[k-a[i]]; d[a[i]]++; } cout << res; return 0; } ``` ::: ### Subtask 3 Vì sử dụng mảng đếm nên nếu phần tử $a_i$ lớn sẽ dẫn đến tràn mảng. Lúc này ta thay thể mảng đếm bằng cấu trúc dữ liệu `map`. Độ phức tạp: $O\left(n \log n \right)$. :::success :::spoiler Code ``` cpp=1 #include <bits/stdc++.h> using namespace std; const int N = 1e5; int n; long long res, k; int a[N+5]; map<long long, int> mp; int main() { cin >> n >> k; for (int i = 1; i <= n; i++){ cin >> a[i]; res += mp[k-a[i]]; mp[a[i]]++; } cout << res; return 0; } ``` ::: ## Bài 4 ### Tóm tắt đề bài Cho mảng $a$ gồm $n$ phần tử hãy tìm dãy con dài nhất của $a$ sao cho các phần tử liền kề trong dãy con thỏa mãn các điều kiện sau: - $0 \lt |i-j| \le 10$ - $|a_j - a_i|>0$ - $|a_j - a_i|$ là một số chính phương. **Dữ liệu đảm bảo:** $1 \le 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 $1 \le n \le 20$. - $40\%$ số điểm tương ứng với $1 \le n \le 10^3$. - $40\%$ số điểm không có ràng buộc gì. ### Subtask 1 Ta sử dụng thuật toán quay lui sinh ra tất cả các dãy con của mảng $a$, kiểm tra điều kiện rồi lấy đáp án. **Độ phức tạp thời gian:** $O(2^n)$. :::success :::spoiler Code ```cpp = 1 #include <bits/stdc++.h> using namespace std; const int N = 20; int n, res; int a[N+5]; vector<int> vct; bool chinh_phuong(int x) { int v = sqrt(x); return v*v == x; } void back_track(int x) { if (x > n) { res = max(res, (int)vct.size()); } else { for (int i=0; i<=1; i++){ if (i==1) { if (vct.size() > 0) { if (x-vct.back() > 10) continue; if (abs(a[x] - a[vct.back()]) == 0) continue; if (!chinh_phuong(abs(a[x] - a[vct.back()])) ) continue; } vct.push_back(x); back_track(x+1); vct.pop_back(); } else back_track(x+1); } } } int main() { cin>>n; for(int i=1;i<=n;i++){ cin>>a[i]; } back_track(1); cout<<res; return 0; } ``` ::: ### Subtask 2 Sử dụng thuật toán quy hoạch động. - **Định nghĩa:** ==$f_i$== là dãy con dài nhất thỏa mã điều kiện kết thúc tại $i$. - **Khởi gán:** ==$f_i = 1$== (dãy con luôn có ít nhất 1 phần tử là $i$). Ta duyệt các cặp $(i,j)$. - **Công thức:** ==$f_i= \max(f_j+1)$== nếu $(i,j)$ thỏa mãn điều kiện đề bài (thêm $a_i$ vào cuối dãy con kết thúc tại $j$ tạo ra dãy con mới kết thúc tại $i$). Độ phức tạp: $O\left(n^2\right)$. :::success :::spoiler Code ```cpp = 1 #include <bits/stdc++.h> using namespace std; const int N = 1e3; int n, res; int a[N+5], f[N+5]; bool not_stf(int x) { int v = sqrt(x); return (x == 0 || v*v != x); } int main() { cin >> n; for (int i=1;i<=n;i++) { cin >> a[i]; f[i] = 1; for (int j = 1; j < i; j++){ if (i-j>10 || not_stf(abs(a[i]-a[j]))) continue; f[i] = max(f[i], f[j] + 1); } res = max(res, f[i]); } cout << res; return 0; } ``` ::: ### Subtask 3 Kế thừa tư tưởng của subtask 2. Tận dụng điều kiện $0 \lt |i-j| \le 10$, ta chỉ duyệt $j$ từ $i-10$ đến $i-1$. Độ phức tạp: $O\left(n \times 10\right)$. :::success :::spoiler Code ``` cpp=1 #include <bits/stdc++.h> using namespace std; const int N = 1e5; int n, res; int a[N+5], f[N+5]; bool not_stf(int x) { int v = sqrt(x); return x==0 || v*v!=x; } int main() { cin >> n; for (int i = 1; i <= n; i++) { cin >> a[i]; f[i] = 1; for (int j = max(1, i-10); j < i; j++){ if (not_stf(abs(a[i] - a[j]))) continue; f[i] = max(f[i], f[j]+1); } res = max(res, f[i]); } cout << res; return 0; } ``` :::