Try   HackMD

Đề TS10 - BRVT - 2022: 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 2022 - 2023.

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 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:

1k,a,b1018
ab
.

Ràng buộc:

  • 40%
    số điểm ứng với
    1k,a,b32000
    ;
  • 40%
    số điểm ứng với
    1k,a,b109
    ,
    0ba106
    ;
  • 20%
    số điểm ứng với
    1k,a,b1018
    .

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(ba).

Code
#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,,dk
. Trong đó
d
là số lớn nhất sao cho
dkn
hay
dnk
.

d là một số nguyên nên
d=nk
.

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à:
nk
.

Vậy để giải quyết bài toán trên ta sẽ lấy kết quả:

bka1k

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
(a1)
.

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

O(1).

Code
#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
b
(
a<b
), tìm số
x
không âm nhỏ nhất để
UCLN(a+x,b+x)=ba
.

Yêu cầu: In ra số

x không âm nhỏ nhất tìm được

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

1<a<b1018
x0
.

Subtasks:

  • 50%
    số điểm:
    1<a<b106
    .
  • 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(ba).

Code
#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.

Gọi:

  • u=ab

Khi đó:

amodu=bmodu (
1
).

Đề ra

UCLN(a+x,b+x)=u, từ đó suy ra:

  • (a+x)modu=0
  • (b+x)modu=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)modu=0
.

Ta có:

(a+x)modu=0x=(a)modu.
x0x=((amodu)+u)modu=u(amodu)

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

O(1).

Code
#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
ai+aj=k
(1i<jn)
.

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

1ai109,
2n105
1k2109
.

Ràng buộc:

  • 40%
    số điểm tương ứng với
    2n103,1ai32000
    .
  • 40%
    số điểm tương ứng với
    2n105,1ai106
    .
  • 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(n2).

Code
#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

Nhận xét

Vì các phần từ trong mảng nhỏ hơn

106 nên nếu
k>2.106
thì đáp án là 0.

Vậy ở subtask này ta chỉ cần giải quyết bài toán với

k2.106

Ta cố định từng phần tử

i, lúc này ta cần đếm số lượng phần tử
j
(1j<i)
sao cho:
ai+aj=kaj=kai

Ta có thể xử lý việc này bằng mảng đếm.

Gọi

dx 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à
dkai
.

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

O(n).

Code
#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ử

ai 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(nlogn).

Code
#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<|ij|10
  • |ajai|>0
  • |ajai|
    là một số chính phương.

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

1n105
1ai109
.

Ràng buộc:

  • 20%
    số điểm tương ứng với
    1n20
    .
  • 40%
    số điểm tương ứng với
    1n103
    .
  • 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(2n).

Code
#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:
    fi
    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:
    fi=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:
    fi=max(fj+1)
    nếu
    (i,j)
    thỏa mãn điều kiện đề bài (thêm
    ai
    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(n2).

Code
#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<|ij|10, ta chỉ duyệt
j
từ
i10
đến
i1
.

Độ phức tạp:

O(n×10).

Code
#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; }