Đề thi học sinh giỏi môn Tin học tỉnh Yên Bái năm học 2021-2022 (Full Code)

Phần 1. Đề thi

Câu 1. (6,0 điểm) Đếm ký tự in hoa

Cho một xâu ký tự St có độ dài tối đa 255 ký tự, gồm các ký tự được lấy từ tập: ‘a’ … ‘z’; ‘A’ … ‘Z’; ‘0’ … ‘9’ và dấu cách.

Yêu cầu: Hãy đếm số ký tự chữ cái in hoa trong xâu St.

Dữ liệu: Vào từ tệp văn bản CHAR.INP gồm một dòng duy nhất ghi xâu St.

Kết quả: Ghi ra tệp văn bản CHAR.OUT gồm một số nguyên duy nhất là số lượng ký tự chữ cái in hoa đếm được trong xâu St. Nếu không có ký tự in hoa nào thì in ra -1.

Ví dụ:

CHAR.INPCHAR.OUT
Ki thi chon HSG lop 9 nam 20224

Câu 2. (6,0 điểm) Tặng quà

Để tri ân cho n khách hàng có số hiệu lần lượt từ 1 đến n, công ty XYZ đã chuẩn bị một chiếc hộp đựng n phiếu quà. Mỗi phiếu quà ghi một số nguyên là giá trị của gói quà mà khách hàng sẽ được nhận. Ban tổ chức cho các khách hàng bốc thăm và ghi nhận lại số trên phiếu quà tương ứng để chuẩn bị cho công tác lên nhận quà.

Yêu cầu: Hãy sắp xếp thứ tự khách hàng lên nhận quà theo giá trị quà từ cao xuống thấp.

Dữ liệu: Vào từ tệp văn bản GIFT.INP gồm:

  • Dòng đầu tiên chứa số nguyên n là số lượng khách hàng cần tri ân (0 < n ≤ 10³).
  • Dòng thứ hai chứa n số nguyên a₁, a₂, …, aₙ, trong đó aᵢ là số ghi trên phiếu của khách hàng có số hiệu i (1 ≤ i ≤ n; 0 < aᵢ ≤ 10⁹).

Kết quả: Ghi ra tệp văn bản GIFT.OUT một dòng duy nhất gồm n số nguyên tương ứng là số hiệu của n khách hàng theo thứ tự nhận quà từ cao xuống thấp.

  • Nếu giá trị quà bằng nhau thì khách hàng có số hiệu nhỏ hơn sẽ được lên nhận quà trước.
  • Các số trên một dòng cách nhau bởi khoảng trắng.

Ví dụ:

GIFT.INPGIFT.OUT
5 30 11 40 51 514 5 3 1 2

Giải thích:

  • Giá trị các món quà từ cao xuống thấp là: 51 51 40 30 11
  • Trình tự khách lên nhận quà với số hiệu lần lượt là: 4 5 3 1 2

Câu 3. (5,0 điểm) Ước nguyên tố nhỏ nhất

An là học sinh rất yêu thích môn Toán học nên thường xuyên làm việc với các con số. An vừa học về số nguyên tố và được thầy ra T câu hỏi có dạng như sau:

“Cho trước số nguyên tố X. Hãy cho biết trong đoạn [A..B] có bao nhiêu số nhận X làm ước nguyên tố nhỏ nhất?”.

Yêu cầu: Hãy giúp bạn An trả lời các câu hỏi mà thầy giáo đưa ra.

Dữ liệu: Vào từ tệp văn bản PMIN.INP gồm:

  • Dòng đầu tiên ghi số nguyên T là số lượng câu hỏi (1 ≤ T ≤ 100).
  • T dòng tiếp theo, mỗi dòng là dữ liệu của một câu hỏi gồm 3 số nguyên: X, A, B
    • (2 ≤ X ≤ 10⁵; 1 ≤ A ≤ B ≤ 10⁵).
    • Các số trên một dòng cách nhau bởi khoảng trắng.

Kết quả: Ghi ra tệp văn bản PMIN.OUT gồm:

  • T dòng, dòng thứ i ghi ra số nguyên tương ứng là kết quả của câu hỏi thứ i.

Ví dụ:

PMIN.INPPMIN.OUT
2 2 1 12 5 20 506 2

Giải thích:

  • Trong [1..12]6 số nhận 2 làm ước nguyên tố nhỏ nhất: 2, 4, 6, 8, 10, 12.
  • Trong [20..50]2 số nhận 5 làm ước nguyên tố nhỏ nhất: 25, 35.

Câu 4. (3,0 điểm) Mua đồ

Nhân dịp được đi tham quan một khu du lịch nổi tiếng, ghé vào quầy lưu niệm nhỏ, An nhận thấy có n món đồ, món đồ thứ i có giá trị aᵢ (1 ≤ i ≤ n; 0 < aᵢ ≤ 10⁵).
Với dự định là phải tiêu hết số tiền S để mua đồ lưu niệm làm quà tặng cho các bạn ở nhà, An muốn mua được nhiều món đồ nhất có thể. Biết rằng mỗi món đồ là duy nhất.

Yêu cầu: Hãy giúp An thực hiện dự định của mình.

Dữ liệu: Vào từ tệp văn bản BUY.INP gồm:

  • Dòng đầu tiên ghi số nguyên n là số món đồ và số nguyên S là số tiền mà An sẽ dùng để mua đồ (2 ≤ n ≤ 25; 0 < S ≤ 10⁶).
  • Dòng thứ hai ghi n số nguyên, số nguyên thứ i tương ứng là giá trị của món đồ thứ i.

Kết quả: Ghi ra tệp văn bản BUY.OUT một số nguyên duy nhất là số món đồ nhiều nhất mua được với đúng số tiền S. Nếu không thể mua được thì ghi ra -1.

Lưu ý: Các số trên một dòng cách nhau bởi khoảng trắng.

Ví dụ:

BUY.INPBUY.OUT
5 16 4 3 7 15 93

Giải thích: Với số tiền S = 16, mua được nhiều nhất là 3 món đồ (mua món thứ 1, 2 và 5).

Phần 2. Code tham khảo

Câu 1. (6,0 điểm) Đếm ký tự in hoa

#include <iostream>
#include <string>
using namespace std;

int main() {
    freopen("CHAR.INP", "r", stdin);
    freopen("CHAR.OUT", "w", stdout);

    string St;
    getline(cin, St); // Đọc toàn bộ dòng từ file

    int countUppercase = 0; // Biến đếm số ký tự in hoa

    // Duyệt qua từng ký tự trong xâu St
    for (char c : St) {
        if (c >= 'A' && c <= 'Z') { // Kiểm tra ký tự in hoa
            countUppercase++;
        }
    }

    // Ghi kết quả vào file
    if (countUppercase > 0) {
        cout << countUppercase; // Ghi số lượng ký tự in hoa
    } else {
        cout << -1; // Không có ký tự in hoa
    }

    return 0;
}

Câu 2. (6,0 điểm) Tặng quà

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int main() {
    // Mở file đầu vào và đầu ra
    freopen("GIFT.INP", "r", stdin);
    freopen("GIFT.OUT", "w", stdout);

    int n;
    cin >> n; // Đọc số lượng khách hàng

    // Vector lưu thông tin {giá trị quà, số hiệu khách hàng}
    vector<pair<int, int>> gifts(n);

    for (int i = 0; i < n; ++i) {
        cin >> gifts[i].first; // Giá trị quà
        gifts[i].second = i + 1; // Số hiệu khách hàng (1-based)
    }

    // Sắp xếp theo yêu cầu:
    // 1. Giảm dần theo giá trị quà
    // 2. Tăng dần theo số hiệu khách hàng nếu giá trị quà bằng nhau
    sort(gifts.begin(), gifts.end(), [](const pair<int, int>& a, const pair<int, int>& b) {
        if (a.first != b.first) {
            return a.first > b.first; // Sắp xếp giảm dần theo giá trị quà
        }
        return a.second < b.second; // Sắp xếp tăng dần theo số hiệu khách hàng
    });

    // Ghi kết quả vào file
    for (int i = 0; i < n; ++i) {
        cout << gifts[i].second << " "; // In số hiệu khách hàng
    }

    return 0;
}

Câu 3. (5,0 điểm) Ước nguyên tố nhỏ nhất

#include <iostream>
#include <vector>
using namespace std;

// Hàm sàng Eratosthenes để tìm các số nguyên tố
vector<bool> sieve(int maxLimit) {
    vector<bool> isPrime(maxLimit + 1, true);
    isPrime[0] = isPrime[1] = false; // 0 và 1 không phải số nguyên tố
    for (int i = 2; i * i <= maxLimit; ++i) {
        if (isPrime[i]) {
            for (int j = i * i; j <= maxLimit; j += i) {
                isPrime[j] = false;
            }
        }
    }
    return isPrime;
}

int main() {
    // Mở file đầu vào và đầu ra
    freopen("PMIN.INP", "r", stdin);
    freopen("PMIN.OUT", "w", stdout);

    int T;
    cin >> T; // Số lượng câu hỏi

    // Sàng Eratosthenes để tìm các số nguyên tố
    const int MAX_LIMIT = 1e5;
    vector<bool> isPrime = sieve(MAX_LIMIT);

    while (T--) {
        int X, A, B;
        cin >> X >> A >> B;

        // Kiểm tra X có phải số nguyên tố không
        if (!isPrime[X]) {
            cout << 0 << "\n"; // Nếu X không phải số nguyên tố, kết quả là 0
            continue;
        }

        int count = 0; // Biến đếm số lượng số thỏa mãn

        // Duyệt qua các số trong đoạn [A, B]
        for (int n = A; n <= B; ++n) {
            if (n % X == 0) { // Kiểm tra n có chia hết cho X không
                bool isValid = true;
                // Kiểm tra không có số nguyên tố nào nhỏ hơn X mà n chia hết
                for (int p = 2; p < X; ++p) {
                    if (isPrime[p] && n % p == 0) {
                        isValid = false;
                        break;
                    }
                }
                if (isValid) {
                    count++;
                }
            }
        }

        // Ghi kết quả cho câu hỏi hiện tại
        cout << count << "\n";
    }

    return 0;
}

Câu 4. (3,0 điểm) Mua đồ

Cách 1. Sử dụng thuật toán quy hoạch động:

#include <iostream>
#include <vector>
using namespace std;

int main() {
    // Mở file đầu vào và đầu ra
    freopen("BUY.INP", "r", stdin);
    freopen("BUY.OUT", "w", stdout);

    int n, S;
    cin >> n >> S; // Đọc số món đồ và số tiền

    vector<int> prices(n);
    for (int i = 0; i < n; ++i) {
        cin >> prices[i]; // Đọc giá trị của từng món đồ
    }

    // Khởi tạo bảng DP
    const int INF = -1; // Giá trị khởi tạo cho trường hợp không thể mua
    vector<int> dp(S + 1, INF);
    dp[0] = 0; // Trường hợp tổng giá trị là 0, số món đồ là 0

    // Xây dựng bảng DP
    for (int i = 0; i < n; ++i) {
        int price = prices[i];
        for (int s = S; s >= price; --s) {
            if (dp[s - price] != INF && dp[s - price] + 1 > dp[s]) {
                dp[s] = dp[s - price] + 1;
            }
        }
    }

    // Ghi kết quả vào file
    cout << dp[S];

    return 0;
}

Cách 2. Sử dụng Thuật toán quay lui

#include <iostream>
#include <vector>
using namespace std;

// Biến toàn cục để lưu kết quả tối ưu
int maxItems = -1;

// Hàm quay lui để tìm số món đồ nhiều nhất
void backtrack(int index, int currentSum, int count, const vector<int>& prices, int S) {
    // Nếu tổng hiện tại bằng S, cập nhật kết quả tối ưu
    if (currentSum == S) {
        maxItems = max(maxItems, count);
        return;
    }

    // Nếu vượt quá S hoặc đã duyệt hết danh sách, dừng lại
    if (currentSum > S || index >= prices.size()) {
        return;
    }

    // Thử chọn món đồ hiện tại
    backtrack(index + 1, currentSum + prices[index], count + 1, prices, S);

    // Thử bỏ qua món đồ hiện tại
    backtrack(index + 1, currentSum, count, prices, S);
}

int main() {
    // Mở file đầu vào và đầu ra
    freopen("BUY.INP", "r", stdin);
    freopen("BUY.OUT", "w", stdout);

    int n, S;
    cin >> n >> S; // Đọc số món đồ và số tiền

    vector<int> prices(n);
    for (int i = 0; i < n; ++i) {
        cin >> prices[i]; // Đọc giá trị của từng món đồ
    }

    // Gọi hàm quay lui để tìm số món đồ nhiều nhất
    backtrack(0, 0, 0, prices, S);

    // Ghi kết quả vào file
    cout << maxItems;

    return 0;
}