Try   HackMD

Đề TS10 - BRVT - 2021: Editorial

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 2021 - 2022.

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)

Bài 1:

Tóm tắt đề bài

Cho số nguyên dương

n. Yêu cầu tính:
S=23+34++n1nT=13132+133+132n1132n

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

3n100.

Tính S

Cho biến

i chạy vòng lặp từ
3
đến
n1
, cộng
i1i
vào kết quả.

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

O(n).

Tính T

Nhận xét:

13i=13i113

Với mỗi số hạng từ

1 đến
2n
, duy trì một biến
cur=13i
là giá trị của số hạng đang xét, nếu
i
lẻ
, cộng
cur
vào đáp án, ngược lại trừ đáp án đi
cur
.

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

O(n).

Mã nguồn mẫu

Độ phức tạp thời gian của cả bài toán là tổng độ phức tạp thời gian của từng yêu cầu.

Code
#include <bits/stdc++.h>
using namespace std;

int main(){
    int n;
    cin >> n;
    
    double S = 0.0, T = 0.0;
    
    for (int i = 3; i <= n; i++) {
        S += 1.0 * (i-1) / i;
    }

    double cur = 1.0 / 3;
    for (int i = 1; i <= 2*n; i++) {
        if (i % 2 == 1 {
            T += cur;
        }
        else {
            T -= cur;
        }
        cur /= 3;
    }

    cout << fixed << setprecision(5) << S << '\n' << T;

    return 0;
}

Bài 2:

Tóm tắt đề bài

Cho số nguyên dương

n
a
, thực hiện các yêu cầu:

  • Tìm số nguyên
    k
    là số
    n
    sau khi đã xóa đi tất cả các số
    0
    .
  • Tìm chữ số tận cùng của
    an
    .

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

2n1018
1a100
.

Yêu cầu 1

Dùng cấu trúc dữ liệu vector, lần lượt tách và đưa từng chữ số sau cùng của

n (tức là
nmod10
) vào vector (ngoại trừ các chữ số
0
). Sau đó, loại bỏ chữ số đó đi bằng cách chia
n
cho
10
.

Yêu cầu 2

Nhận xét

Đáp an của bài toán là

anmod10.

Để tính

anmod10 một cách nhanh chóng, dùng lũy thừa nhị phân kết hợp với modulo trong lúc tính.

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

O(logn)

Mã nguồn mẫu

Độ phức tạp thời gian của cả bài toán là tổng độ phức tạp thời gian của từng yêu cầu.

Code
#include <bits/stdc++.h>
using namespace std;

long long n;
int a;

int Pow(int a, long long n) {
    if (n == 0) {
        return 1;
    }
    
    int tmp = Pow(a, n/2);
    tmp = tmp * tmp;
    if (n % 2 == 1) {
        tmp = tmp * a;
    }
    
    return tmp % 10;
}

int main(){
    cin >> n >> a;

    //Yêu cầu 1
    long long tmp = n;
    vector<int> digits;
    
    while (tmp > 0) {
        if (tmp % 10 != 0) {
            digits.push_back(tmp % 10);
        }
        tmp /= 10;
    }
    
    for (int i = digits.size()-1; i >= 0; i--) {
        cout << digits[i];
    }
    
    cout << '\n';

    //Yêu cầu 2
    cout << Pow(a, n);

    return 0;
}

Bài 3:

Tóm tắt đề bài

Cho hai số nguyên dương

n,
k
và dãy số nguyên nguyên dương
A1,A2,A3,,An
. Thực hiện các yêu cầu:

  • In ra các số
    Ai
    có dạng
    Ai=5h+2(hN)
    .
  • In ra số lượng số nguyên tố trong dãy.
  • In ra khoảng cách lớn nhất giữa hai số chính phương (số lượng phần tử nhiều nhất nằm giữa hai số chính phương) trong dãy. In ra
    1
    nếu không tồn tại hai số chính phương.
  • Chia dãy số thành hai phần sao cho: Phần thứ nhất có
    k
    phần tử, phần thứ hai có
    nk
    phần tử. In ra độ chênh lệch lớn nhất giữa tổng của hai phần.

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

2kn105
1Ai107
.

Yêu cầu 1

Nhận xét

Ai=5h+25h=Ai2h=Ai25
h
nguyên, do đó
Ai2
chia hết cho
5
, hay
(Ai2)mod5=0
.

Như vậy, ta duyệt qua dãy và in ra các số thỏa mãn điều kiện trên.

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

O(n).

Yêu cầu 2

Ai107, có thể dùng kĩ thuật sàng nguyên tố, sau đó với mỗi số
Ai
kiểm tra nhanh xem nó có phải số nguyên tố hay không và đếm.

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

O(nloglogn).

Yêu cầu 3

Kiểm tra số chính phương

Số chính phương là bình phương của một số nguyên.

Ta có thể kiểm tra một số

x có phải số chính phương hay không bằng cách lấy bình phương phần nguyên của
x
so sánh với
x
.
Nghĩa là,
x
chính phương khi và chỉ khi:
x2=x

Để khoảng cách giữa hai số chính phương được chọn là xa nhất, ta chọn số chính phương đầu tiêncuối cùng của dãy.

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

O(n).

Yêu cầu 4

Nhận xét:

Chênh lệch giữa hai phần là lớn nhất khi một phần đạt tổng lớn nhất có thể và phần còn lại phải nhỏ nhất có thể.

Dễ dàng nhận thấy:

  • Nếu
    knk
    , ta nhóm
    k
    phần tử lớn nhất
    chung với nhau và
    nk
    phần tử nhỏ nhất
    chung với nhau
  • Nếu
    k<nk
    , ta nhóm
    nk
    phần tử lớn nhất
    chung với nhau và
    k
    phần tử nhỏ nhất
    chung với nhau

Có thể tìm nhanh tập các phần tử lớn nhất hoặc nhỏ nhát bằng cách sắp xếp lại dãy số.

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

O(nlogn)

Mã nguồn mẫu

Độ phức tạp thời gian của cả bài toán là tổng độ phức tạp thời gian của từng yêu cầu.

Code
#include <bits/stdc++.h>
using namespace std;

const int N = 1e5 + 5;
const int A = 1e7 + 5;
int n, a[N], k, isPrime[A];

bool isSquare(int n) {
    int tmp = sqrt(n);
    
    if (tmp * tmp == n) {
        return 1;
    }
    
    return 0;
}

main(){
    cin >> n >> k;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
    }

    /// Yêu cầu 1
        for (int i = 1; i <= n; i++) {
            if ((a[i]-2) % 5 == 0) cout << a[i] << ' ';
        }
    
        cout << '\n';

    /// Yêu cầu 2
        isPrime[1] = 1;
        for (int i = 2; i*i <= 1e7; i++) {
            if (isPrime[i] == 0) {
                for (int j = i*i; j <= 1e7; j += i) {
                    isPrime[j] = 1;
                }
            }
        }

        int cnt = 0;
        for (int i = 1; i <= n; i++) {
            if (isPrime[a[i]] == 0) {
                cnt++;
            }
        }
    
        cout << cnt << '\n';

    /// Yêu cầu 3
        int L = -1, R = -1;
        for (int i = 1; i <= n; i++) {
            if (isSquare(a[i])) {
                if (L == -1) L = i;
                R = i;
            }
        }
    
        if (L == R) {
            cout << -1 << '\n';
        }
        else {
            cout << R - L - 1 << '\n';
        }

    /// Yêu cầu 4
        sort (a+1, a+1+n);
        k = min(k, n-k);
    
        long long S_max = 0, S_min = 0;
        for (int i = 1; i <= n; i++) {
            if (i > k) {
                S_max += a[i];
            }
            else {
                S_min += a[i];
            }
        }
    
        cout << S_max - S_min;

    return 0;
}

Bài 4:

Tóm tắt đề bài

Cho số nguyên

n và dãy số nguyên
A1,A2,A3,,An
.

Yêu cầu: Chia dãy

A thành hai phần sao cho tổng của mỗi phần đều là số nguyên tốchênh lệch giữa hai phần là nhỏ nhất. In ra
1
nếu không có cách chia.

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

2n100
1Ai104
.

Lời giải

Áp dụng quy hoạch động cái túi.

Định nghĩa:

Fi là khả năng tạo ra tổng bằng
i
từ các phần tử trong dãy:

  • Fi=1
    , có thể tạo được tổng
    i
    .
  • Fi=0
    , không thể tạo được tổng
    i
    .

Khởi gán:

F0=1 (không sử dụng phần tử nào).

Công thức: Xét mọi phần tử

Ai và mọi tổng
j
bất kỳ. Nếu
jAi0
FjAi=1
thì
Fj=1
.

Lưu ý

Ta tính

Fj dựa vào
FjAi
jAi<j
nên cần duyệt
j
giảm dần, để khi lấy
FjAi
ta đảm bảo đó là đáp án chưa được cập nhật bởi
Ai
.

Nhận xét

Gọi tổng của cả dãy số là

sum.

Chênh lệch giữa hai phần là nhỏ nhất khi tổng của hai phần gần với

sum2 nhất.

Trong hai phần, sẽ luôn có một phần với tổng

Dsum2 và phần còn lại là
sumD
lớn hơn hoặc bằng
sum2
.

Không mất tính tổng quát, ta chỉ cần tính

Dsum2. Đáp án tối ưu khi D lớn nhất.

Đáp án hợp lệ khi

D
sumD
là số nguyên tố, ta có thể kiểm tra điều này bằng kĩ thuật sàng nguyên tố.

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

O(ni=1nAi+nloglogn)

ni=1nAi: Quy hoạch động.
nloglogn
: Sàng nguyên tố.

Mã nguồn mẫu

Code
#include <bits/stdc++.h>
using namespace std;

const int N = 102 + 5;
const int S = 1e6 + 5;
int n, isPrime[S], a[N], f[S], sum;

int main(){
    isPrime[1] = 1;
    for (int i = 2; i*i <= 1e6; i++) {
        if (isPrime[i] == 0) {
            for (int j = i*i; j <= 1e6; j += i) {
                isPrime[j] = 1;
            }
        }
    }

    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
        sum += a[i];
    }

    f[0] = 1;
    for (int i = 1; i <= n; i++) {
        for (int j = 1e6; j >= a[i]; j--) {
            f[j] = max(f[j], f[j - a[i]]);
        }
    }

    int res = -1;
    for (int i = 1; i <= sum/2; i++) {
        if (isPrime[i] == 0 && f[i] == 1 && isPrime[sum-i] == 0 && f[sum-i] == 1) {
            res = i;
        }
    }

    if (res == -1) {
        cout << -1;
    }
    else {
        cout << res << ' ' << sum - res << ' ' << sum - 2*res;
    }
    
    return 0;
}