Try   HackMD

THI THỬ HSG TỈNH K9 - 2025 - II

Thông tin

Sau đây là lời giải của đề thi thử Kỳ thi Chọn HSG tỉnh Bà Rịa - Vũng Tàu K9 lần II (được chuẩn bị theo ma trận kiến thức của Sở GD&ĐT tỉnh Bà Rịa - Vũng Tàu)

Thời gian: 20:00 - 22:30 ngày 02/03/2025

Bạn đọc có thể nộp và chấm bài TẠI ĐÂY

Chuẩn bị bởi team Logica, thuộc khuôn khổ dự án The Algitect (ALGI Project)

Lưu ý: Lời giải này chỉ hướng đến việc AC (tức là đạt trọn điểm) các bài.

Bài 1: Nỗi sợ của loài rồng (5 điểm)

Tóm tắt đề bài:

n con rồng lần lượt bay đến tấn công lâu đài của Trâm.

  • Những con rồng thứ
    k,k2,k3,
    sẽ bị kẹp đuôi.
  • Những con rồng thứ
    l,l2,l3,
    sẽ bị cắt vuốt.

Yêu cầu: Đếm số lượng con rồng đã bị Trâm kẹp đuôi hoặc cắt vuốt.

Dữ liệu bảo đảm:

1k,lkl,n1018

Subtasks:

  • 30%
    số điểm:
    1k,ln106
    .
  • 30%
    số điểm:
    kl106
    .
  • 40%
    số điểm: Không có ràng buộc gì thêm.

Mô hình hóa bài toán:

Bài toán đơn giản là đếm số lượng số từ

1 đến
n
chia hết cho
k
hoặc
l
.

Lưu ý

Những số chia hết cho cả

k
l
chỉ được xem là
1
số.

Lời giải

Tip

Ta có công thức sau:

cntx=nx
Với
cntx
là số lượng bội của
x
từ
1
đến
n
.

Như vậy, ta có thể tính được số lượng bội của

k
l
là:
nk+nl

Tuy nhiên, với phép tính trên, những số vừa chia hết cho

k vừa chia hết cho
l
đang bị đếm lặp lại
2
lần và cần trừ đi.

Nhận xét

Số nhỏ nhất vừa chia hết cho

k và vừa chia hết cho
l
bội chung nhỏ nhất của
k
l

Vì vậy, bội của bội chung nhỏ nhất của

k
l
là những số chúng ta cần trừ đi

Ta tính được bội chung nhỏ nhất của

k
l
bằng:
klGCD(k,l)

Kết quả bài toán là:

nk+nlnklGCD(k,l)

Độ phức tạp thời gian:

O(logmin(k,l)).

Độ phức tạp trên là độ phức tạp để tính

GCD bằng thuật toán Euclid.

Code
#include <bits/stdc++.h> using namespace std; long long n, k, l; long long __lcm(long long a, long long b) { return (a / __gcd(a, b)) * b; } int main() { cin >> n >> k >> l; cout << (n / k) + (n / l) - (n / __lcm(k, l)); return 0; }

Bài 2: Mật mã cổ đại (5 điểm)

Tóm tắt đề bài:

Cho

q truy vấn, mỗi truy vấn gồm một số nguyên
n
.

Yêu cầu: In ra các ước nguyên tố của

n theo thứ tự giảm dần.

Dữ liệu bảo đảm:

1q105
1n106
.

Subtasks:

  • 20%
    số điểm:
    1q,n100
    .
  • 40%
    số điểm:
    1q104
    .
  • 40%
    số điểm: Không có ràng buộc gì thêm.

Mô hình hóa bài toán:

Thực chất, bài toán yêu cầu phân tích thừa số nguyên tố của

n.

Lời giải:

Định nghĩa

Ei là ước nguyên tố nhỏ nhất của
i
.

VD:

E2=2,E9=3.

Giả sử đã tính được

Ei với
i
từ
1
đến
n
, có thể tìm được tất cả các ước số nguyên tố của
n
bằng cách không ngừng chia
n
cho
En
đến khi
n=1
.

VD: Phân tích thừa số nguyên tố của

n=40

  • E40=2n=402=20
  • E20=2n=202=10
  • E10=2n=102=5
  • E5=5n=55=1

n=40 có các ước nguyên tố là
2
5
.

Tip

Tạm gọi việc thực hiện tính trước mảng

Esàng ước nguyên tố. Đúng như tên gọi của nó, phương pháp này áp dụng tư tưởng của thuật toán sàng nguyên tố.

Cụ thể, ta duyệt

i tăng dần từ
2
, nếu
i
chưa tìm được ước nguyên tố
(Ei=0)
, ta duyệt qua tất cả các bội của
j
(i,i2,i3,)
và đánh dấu
Ej=i
.

Vì với mỗi số, ta duyệt qua các bội của nó, do đó thuật toán trên có độ phức tạp:

O(n1+n2+n2)O(nlogn)
Với
n
là giá trị lớn nhất cần sàng tới.

Tuy nhiên, trong thực tế, thuật toán trên chạy nhanh hơn rất nhiều vì ta chỉ duyệt qua bội của những số nguyên tố.

Độ phức tạp thời gian:

O(nlogn).

Code
#include <bits/stdc++.h> using namespace std; const int N = 1e6; int q, n, E[N+1]; void sieve() { for (int i = 2; i <= N; ++i) { if (!E[i]) { for (int j = i; j <= N; j += i) { E[j] = i; } } } } int main() { sieve(); cin >> q; while (q--) { cin >> n; while (n > 1) { int p = E[n]; cout << p << " "; while (n % p == 0) { n /= curr; } } cout << "\n"; } return 0; }

Bài 3: Chuyển nhà (5 điểm)

Tóm tắt đề bài:

Khoa có

n món đồ, món đồ thứ
i
có khối lượng là
Ai
. Cậu cần xếp các món đồ vào thùng có sức chứa tối đa là
k
.

Yêu cầu: Đếm số cách để Khoa chọn hai món đồ bỏ vào thùng.

Dữ liệu bảo đảm:

1n106
1Ai,k109
.

Subtasks:

  • 40%
    số điểm:
    2n103
    .
  • 60%
    số điểm không có ràng buộc gì thêm.

Mô hình hóa bài toán

Thực chất, bài toán yêu cầu đếm số cặp

i<j thỏa
Ai+Ajk
.

Lời giải:

Xét trường hợp ta cố định

Ai, khi đó cần đếm số lượng
j
thỏa:

  • j>i
    .
  • AjkAi
    .

Điều này có thể thực hiện bằng tìm kiếm nhị phân cơ bản.

Caution

Có thể, khoảng

Aj thỏa mãn chứa cả
Ai
mà ta đang cố định. Khi đó, cần trừ
Ai
ra.

Độ phức tạp thời gian:

O(nlogn).

Code
#include <bits/stdc++.h> using namespace std; const int N = 1e6; ll n, k, ans, A[N+1]; int main() { cin >> n >> k; for (int i = 1; i <= n; i++){ cin >> A[i]; } sort(A+1, A+1+n); for(int i = 1; i < n; i++){ if (A[i] >= k) break; int pos = upper_bound(A+1, A+1+n, k - a[i]) - A - 1; ans += max(0, pos - i); } cout << ans; return 0; }

Bài 4: Hộp ma thuật (5 điểm)

Tóm tắt đề bài:

Tí có một chiếc hộp ma thuật và được đưa cho một hằng số

K. Có
q
truy vấn, mỗi truy vấn thuộc một trong hai dạng:

  • + x: Thêm một viên ngọc có sức mạnh
    x
    vào hộp.
  • - x: Lấy ra một viên ngọc có sức mạnh
    x
    (đảm bảo có ít nhất một viên trong hộp).

Yêu cầu: Với mỗi truy vấn, tính số cách lấy một số viên ngọc trong hộp để tổng sức mạnh bằng

K. In ra số cách chia dư cho
998244353

Dữ liệu bảo đảm:

1q105
2n106
.

Subtasks:

  • 20%
    số điểm:
    1q20
    .
  • 40%
    số điểm:
    1q,k200
    .
  • 40%
    số điểm: Không có ràng buộc gì thêm.

Mô hình hóa bài toán:

Với mỗi truy vấn, đếm số cách chọn một tập con trong tập hợp các số để tổng bằng

K.

Đây là một bài toán quy hoạch động cái túi (balo) điển hình.

Lời giải:

  • Định nghĩa:
    dpi
    là số cách tạo tổng bằng
    i
    .
  • Khởi gán:
    • dp0=1
      .
    • dpi=0
      với mọi
      i0
      .
  • Công thức:
    • Khi thêm một giá trị
      x
      :
      dpi=dpi+dpix
      với
      ix
      .

    Với những cách tạo ra tổng

    ix ban đầu, có thể thêm giá trị
    x
    để tạo tổng
    i
    .

    • Khi xóa đi một giá trị
      x
      :
      dpi=dpidpix

    Với những cách tạo ra tổng

    ix ban đầu, nay mất đi một giá trị
    x
    để tạo tổng
    i
    .

  • Kết quả:
    dpK
    .

Lưu ý:

  • Đối với truy vấn - x cần phải duyệt
    i
    giảm dần để đảm bảo
    dpix
    đang lưu trạng thái của truy vấn trước chứ không phải truy vấn hiện tại.
  • Ngược lại, đối với truy vấn + x cần phải duyệt
    i
    tăng dần để
    dpix
    lưu trạng thái khi đã xét thêm giá trị
    x
    .

Độ phức tạp thời gian:

O(qK).

Code
#include <bits/stdc++.h> using namespace std; const int N = 5000; const long long M = 998244353; long long q, k, dp[N+1]; int32_t main() { dp[0] = 1; cin >> q >> k; while (q--) { char chr; int x; cin >> chr >> x; if (chr == '+') for (int i = k; i >= x; i--) (dp[i] += dp[i-x]) %= M; if (chr == '-') for (int i = x; i <= k; i++) (((dp[i] -= dp[i-x]) %= M) += M) %= M; cout << dp[k] << "\n"; } return 0; }