Thông tin
Sau đây là lời giải của Kỳ thi Chọn HSG10-11 tỉnh Bà Rịa - Vũng Tàu năm học 2024 - 2025.
Thời gian thi: 08:00 - 11:00 ngày 27/03/2025.
Bạn đọc có thể nộp và chấm bài (test tự sinh) TẠI ĐÂY.
Chuẩn bị bởi team Logica, thuộc khuôn khổ dự án The Algitect (ALGI Project)
Cho ba số nguyên dương
Yêu cầu: Đếm số lượng số nguyên
Dữ liệu đảm bảo:
Subtasks:
Duyệt qua từng số nguyên
Độ phức tạp thời gian:
#include <bits/stdc++.h>
using namespace std
long long n, a, b, res;
int main() {
cin >> n >> a >> b;
for (long long i = 1; i <= n; i++)
if (i % a == i % b)
res++;
cout << res;
return 0;
}
Nhận xét
Giả sử ta có:
Khi đó:
Nói cách khác,
Hay tương ứng với mỗi bội
Cảnh báo
Tuy nhiên, điều này không đúng với:
Chúng ta cần xét riêng hai trường hợp bội
Ta xét riêng ba trường hợp:
Tính nhanh BCNN
Ta có:
Có thể tính nhanh __gcd()
có sẵn của C++.
Lưu ý: Với giới hạn dữ liệu lớn, nếu cứ mặc kệ mà tính BCNN, giá trị có thể lên đến
Do đó ta cần xử lý khéo bằng cách dừng nếu nhận thấy số quá lớn!
Đáp án:
Độ phức tạp thời gian:
Độ phức tạp của thuật toán Euclid để tìm BCNN.
#include <bits/stdc++.h>
using namespace std;
long long n, a, b;
long long __lcm(long long x, long long y) {
long long z = x / __gcd(x, y);
if (z > n / y) return n + 1;
return (z * y);
}
int main() {
cin >> n >> a >> b;
long long t = __lcm(a, b);
if (a > b) swap(a, b);
long long res = min(n, (a - 1)) + ((max(0LL, n - a + 1) / t) * a);
if ((n / t) * t > 0 && (n / t) * t + a - 1 > n)
res += (n - (n / t) * t + 1);
cout << res;
return 0;
}
Cho
Yêu cầu: Với mỗi
Dữ liệu đảm bảo:
Subtasks:
Duyệt qua từng số
Độ phức tạp thời gian:
#include <bits/stdc++.h>
using namespace std;
int t, a, b;
bool isPrime(int num) {
if (num < 2) return 0;
for (int i = 2; i*i <= num; i++)
if (num % i == 0)
return 0;
return 1;
}
int main() {
cin >> t;
while (t--) {
cin >> a >> b;
int res = 0;
for (int i = a; i <= b; i++)
if (isPrime(i))
res += i;
cout << res << "\n";
}
return 0;
}
Kế thừa tư tưởng từ subtask trước, tuy nhiên ta cần cải tiến việc kiểm tra tính nguyên tố của số
Nhận thấy
Độ phức tạp thời gian:
Duyệt qua đoạn
: .
Sàng số nguyên tố đến cận trên là: .
#include <bits/stdc++.h>
using namespace std;
const int MX = 1e4;
int t, a, b;
bool E[MX+1];
void sieve() {
E[0] = E[1] = 1;
for (int i = 2; i*i <= MX; i++)
if (!E[i])
for (int j = i*i; j <= MX; j += i)
E[j] = 1;
}
int main() {
sieve();
cin >> t;
while (t--) {
cin >> a >> b;
int res = 0;
for (int i = a; i <= b; i++)
if (!E[i])
res += i;
cout << res << "\n";
}
return 0;
}
Kế thừa tư tưởng từ subtask trước, tuy nhiên ta cần cải tiến việc duyệt qua đoạn để tính tổng các số nguyên tố. Thay vào đó, ta có thể dùng mảng tiền tố (prefix sum):
Độ phức tạp thời gian:
#include <bits/stdc++.h>
using namespace std;
const int MX = 1e5;
int t, a, b, S[MX+1];
bool E[MX+1];
void sieve() {
E[0] = E[1] = 1;
for (int i = 2; i <= MX; i++)
if (!E[i])
for (int j = i*i; j <= MX; j += i)
E[j] = 1;
}
int main() {
sieve();
for (int i = 1; i <= MX; i++) {
S[i] = S[i-1];
if (!E[i])
S[i] += i;
}
cin >> t;
while (t--) {
cin >> a >> b;
cout << S[b] - S[a-1] << "\n";
}
return 0;
}
Cho dãy số nguyên dương
Yêu cầu: Cho
Dữ liệu đảm bảo:
Subtask:
Bài toán thực chất yêu cầu kiểm tra trong đoạn
Gọi map
được cung cấp trong C++ STL.
Với mỗi truy vấn, duyệt từ
Cuối cùng, kiểm tra xem có giá trị nào xuất hiện lẻ lần hay không.
Độ phức tạp thời gian:
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5;
int t, n, A[N+1];
bool check(int l, int r) {
map<int, int> mp;
for (int i = l; i <= r; i++)
mp[A[i]]++;
for (int i = l; i <= r; i++)
if (mp[A[i]] % 2 != 0)
return 0;
return 1;
}
int main() {
cin >> n >> t;
for (int i = 1; i <= n; i++) cin >> A[i];
while (t--) {
int l, r;
cin >> l >> r;
if (check(l, r)) cout << "YES\n";
else cout << "NO\n";
}
return 0;
}
Kế thừa tư tưởng từ subtask trước.
Nhận xét
Gọi
Với mỗi truy vấn
Độ phức tạp thời gian:
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5;
int t, n, A[N+1], S[11][N+1] ;
bool check(int l, int r) {
for (int x = 1; x <= 10; x++)
if ((S[x][r] - S[x][l-1]) % 2 != 0)
return 0;
return 1;
}
int main() {
cin >> n >> t;
for (int i = 1; i <= n; i++) cin >> A[i];
for (int x = 1; x <= 10; x++)
for (int i = 1; i <= n; i++) {
S[x][i] = S[x][i-1];
if (A[i] == x) S[x][i]++;
}
while (t--) {
int l, r;
cin >> l >> r;
if (check(l, r)) cout << "YES\n";
else cout << "NO\n";
}
return 0;
}
Thuật toán
Bài này yêu cầu sử dụng kỹ thuật XOR hashing.
Sau đây, lời giải sẽ sử dụng ký hiệu
Ta có thể khai thác tính chất của phép XOR:
Ngoài ra, khi ta cần lấy tổng XOR của một đoạn
Một đoạn
Lưu ý: Thuật toán trên không luôn luôn đúng, vì có thể xảy ra collision (nói đơn giản là trùng giá trị).
Ví dụ:
Để hạn chế collision, ta kết hợp hashing, nghĩa là gán mỗi giá trị
Độ phức tạp thời gian:
#include <bits/stdc++.h>
using namespace std;
mt19937_64 rng(time(0));
const int N = 1e6;
int t, n, A[N+1], S[N+1];
map<int, int> val;
int main() {
cin >> n >> t;
for (int i = 1; i <= n; i++) {
cin >> A[i];
if (!val.count(A[i])) val[A[i]] = rng();
A[i] = val[A[i]];
}
for (int i = 1; i <= n; i++)
S[i] = S[i-1] ^ A[i];
while (t--) {
int l, r;
cin >> l >> r;
if ((S[r] ^ S[l-1]) == 0) cout << "YES\n";
else cout << "NO\n";
}
return 0;
}
Cho
Cho
Nếu thiết bị
Yêu cầu: Chọn ra
Dữ liệu đảm bảo:
Subtask:
Áp dụng thuật toán quay lui, tạo từng bộ
Độ phức tạp thời gian theo cách trên:
Trong đó
Với độ phức tạp như trên ta chưa thể lấy được trọn điểm của subtask này.
Nhận xét
Với mỗi cặp bộ nguồn
Giải thích tính đúng đắn của nhận xét trên
Ta có độ gây hại của hai thiết bị lần lượt là:
Ta xét hiệu của
Do
Trường hợp 1:
Trường hợp 2:
Khi đó,ta có:
Vậy hiệu của
Vậy nhận xét trên đúng trong mọi trường hợp.
Với nhận xét trên, ta có thể giải bài toán này như sau:
Nếu coi
và nằm trên hai đường thẳng, việc ghép là nối hai điểm lại với nhau, thì sẽ không có đoạn thẳng nào cắt nhau.
Độ phức tạp thời gian:
#include <bits/stdc++.h>
using namespace std;
const int N = 1e3, MX = 1e18;
int res = MX, n, k, P[N+1], T[N+1];
void backTrack(int id, int last, int cost) {
if (cost > res) return;
if (id > k) {
res = min(res, cost);
return;
}
for (int i = last + 1; i <= n; i++)
backTrack(id+1, i, cost + abs(P[i] - T[id]));
}
int main() {
cin >> n >> k;
for (int i = 1; i <= n; i++) cin >> P[i];
for (int i = 1; i <= k; i++) cin >> T[i];
sort(P+1, P+1+n);
sort(T+1, T+1+k);
backTrack(1, 0, 0);
cout << res;
return 0;
}
Kế thừa tư tưởng của subtask trước, nhưng ta áp dụng quy hoạch động.
Độ phức tạp thời gian:
#include <bits/stdc++.h>
using namespace std;
const int N = 1e3;
int n, m, k;
int p[N+5], t[N+5], f[N+5][N+5];
int main() {
cin >> n >> k;
for (int i = 1; i <= n; i++) {
cin >> p[i];
}
for (int i = 1; i <= k; i++) {
cin >> t[i];
}
sort(p+1, p+1+n);
sort(t+1, t+1+k);
//60 ~ INF
for (int i = 0; i <= k; i++)
for (int j = 0; j <= n; j++)
f[i][j] = 1e9;
for (int i = 0; i <= n; i++){
f[0][i] = 0;
}
for (int i = 1; i <= k; i++) {
for (int j = 1; j <= n; j++) {
f[i][j] = min(f[i][j-1], f[i-1][j-1] + abs(t[i] - p[j]));
}
}
cout << f[k][n];
return 0;
}