Đề thi HSG môn Tin học (Đề thi thử)

Đây là bộ đề thi dành cho các học sinh bậc THCS tham khảo trước khi bước vào kỳ thi học sinh giỏi các cấp. Với một bộ đề khá dàn trải các loại kiến thức các em đã được ôn luyện trong quá trình luyện thi. Hy vọng sẽ là nguồn kiến thức bổ ích giúp các em rèn luyện thêm kỹ năng giải quyết các bài toán từ dễ đến khó trong các kỳ thi học sinh giỏi trong tương lai.

Đề tham khảo

Bài 1. Cặp số có tích lớn nhất

John có ba số nguyên a, b và c. Jonh muốn tìm hai số có tích lớn nhất và đưa ra tích lớn nhất tìm được ra. Ví dụ: a = 5, b = 6, c = 3. Kết quả: s = 30.

Dữ liệu: Vào từ tệp AMULB.INP số nguyên dương a, b và c (a, b, c ≤ 10^9).

Kết quả: Ghi ra tệp AMULB.OUT tích lớn nhất mà John tìm được.

Ví dụ:

AMULB.INP AMULB.OUT
9 8 10 90

Bài 2. Đếm số lượng ước

John và Jame chơi trò đếm ước nguyên dương của một số. John viết lên bảng số N, Jame viết lên bảng số M. Người thắng cuộc là người có số nguyên có nhiều ước nguyên dương nhất.

Yêu cầu: Cho N và M. Hãy xác định tên người thắng cuộc và số lượng ước nhiều nhất tìm được. Nếu John và Jame ghi hai số có số lượng ước nguyên dương bằng nhau thì ghi ra EQUAL. Ví dụ: John ghi số N =10 có các ước nguyên dương là 1, 2, 5, 10. John có 4 ước nguyên dương. Jame ghi số N =16 có các ước nguyên dương là 1, 2, 4, 8, 16. Jame có 5 ước nguyên dương.

Dữ liệu: Vào từ tệp BDIVMN.INP số nguyên dương N, M (N, M ≤ 10^9).

Kết quả: Ghi ra tệp BDIVMN.OUT tên người thắng cuộc và số lượng ước nhiều nhất tìm được. Nếu John và Jame ghi hai số có số lượng ước nguyên dương bằng nhau thì ghi ra EQUAL.

Ví dụ: 

BDIVMN.INP BDIVMN.OUT
10 16 Jame 5
10 15 EQUAL

Bài 3. Số nguyên tố

John và Jame rất thích các số nguyên tố. Số nguyên tố là số nguyên dương lớn hơn 1 và chỉ có hai ước. Dãy các số nguyên tố đầu tiên là: 2, 3, 5, 7, 11, 13, 17, 19, 23, … John chọn các số nguyên tố thuộc đoạn [a, b], Jame chọn các số nguyên tố trong đoạn [c, d]. Người thắng cuộc là người có được nhiều số nguyên tố nhất.

Yêu cầu: Cho a, b, c, d. Hãy xác định tên người thắng cuộc và số lượng số nguyên tố người đó có được.

Dữ liệu: Vào từ tệp CPRIMED.INP số nguyên dương a, b, c, d (a ≤ b ≤ 10^6, c ≤ d ≤ 10^6).

Kết quả: Ghi ra tệp CPRIMED.OUT tên người thắng cuộc và số lượng số nguyên tố nhiều nhất tìm được. Nếu John và Jame ghi hai số có số lượng số nguyên tố bằng nhau thì ghi ra EQUAL.

Ví dụ:

CPRIMED.INP CPRIMED.OUT
1 5 2 6 EQUAL
4 5 8 9 John 1

Bài 4. Đếm xâu

John và Jame chơi trò chơi với các xâu kí tự. John có xâu S1, Jame có xâu S2. Người thắng cuộc là người có xâu xuất hiện nhiều lần nhất trong xâu của người còn lại. Ví dụ: John có xâu S1 = “abababa”, Jame có xâu S2 = “baba” thì Jame là người thắng cuộc vì S2 xuất hiện 2 lần trong S1, còn S1 không xuất hiện lần nào trong S2.

Yêu cầu: Cho S1, S2. Hãy xác định tên người thắng cuộc và số lần xuất hiện xâu của người đó trong xâu còn lại. Nếu John và Jame có hai xâu có số lần xuất hiện xâu này trong xâu kia bằng nhau thì ghi ra EQUAL.

Dữ liệu: Vào từ tệp DCOUNTST.INP gồm:

• Dòng 1: Ghi xâu S1 là xâu của John.

• Dòng 2: Ghi xâu S2 là xâu của Jame. Mỗi xâu dài không quá 255 kí tự.

Kết quả: Ghi ra tệp DCOUNTST.OUT tên người thắng cuộc và số lần xuất hiện xâu của người đó trong xâu còn lại. Nếu John và Jame có hai xâu có số lần xuất hiện xâu này trong xâu kia bằng nhau thì ghi ra EQUAL.

Ví dụ:

DCOUNTST.INP DCOUNTST.OUT
ababab
baba
Jame 2

Bài 5. Tặng quà

Có N món quà, món quà thứ i có giá trị là a[i] (đồng). Bác Jone muốn chia thành hai phần sao cho độ chênh lệch của tổng giá trị món quà của mỗi phần là nhỏ nhất. John và Jame lần lượt nhận được các phần quà này.

Yêu cầu: Cho N và dãy a1, a2,…., aN. Bác Jone muốn biết có bao nhiêu cách chia các món quà sao cho độ chênh lệch của tổng giá trị món quà của mỗi phần là nhỏ nhất và liệt kê các cách chia đó ra.

Dữ liệu: Vào từ tệp EGIFTF.INP gồm:

• Dòng 1: Ghi số nguyên dương N là số lượng món quà (N ≤ 20).

• Dòng 2: Ghi N số nguyên dương phân biệt a[i] ghi giá trị món quà thứ i (a[i] ≤ 10^9).

Kết quả: Ghi ra tệp EGIFTF.OUT gồm:

• Dòng 1: Ghi ra số cách chia quà thỏa mãn và độ chênh lệch nhỏ nhất của hai phần quà đã chia.

• Các dòng tiếp theo, mỗi dòng ghi chỉ số món các món quà mà John nhận được.

Ví dụ:

EGIFTF.INP EGIFTF.OUT
3
3 4 7
1 0
3

 

Code tham khảo cho đề thi thử

Bài 1. Cặp số có tích lớn nhất

Cách 1. Để giải bài toán này, chúng ta chỉ cần tìm ra hai số lớn nhất trong ba số a, b và c và sau đó tính tích của chúng. Code tham khảo:

# Đọc dữ liệu từ file input
with open('AMULB.INP', 'r') as file:
    a, b, c = map(int, file.readline().split())

# Tìm hai số lớn nhất
max1 = max(a, b, c)
if max1 == a:
    max2 = max(b, c)
elif max1 == b:
    max2 = max(a, c)
else:
    max2 = max(a, b)

# Tính tích lớn nhất
product = max1 * max2

# Ghi kết quả ra file output
with open('AMULB.OUT', 'w') as file:
    file.write(str(product))

Cách 2. Nếu sắp xếp các số a, b, c theo thứ tự giảm dần, thì tích lớn nhất sẽ là tích của hai số đầu tiên trong danh sách đã sắp xếp. Code tham khảo:

# Đọc dữ liệu từ file input
with open('AMULB.INP', 'r') as file:
    a, b, c = map(int, file.readline().split())

# Sắp xếp các số giảm dần
numbers = sorted([a, b, c], reverse=True)

# Lấy hai số đầu tiên
max1, max2 = numbers[:2]

# Tính tích lớn nhất
product = max1 * max2

# Ghi kết quả ra file output
with open('AMULB.OUT', 'w') as file:
    file.write(str(product))

Bài 2. Đếm số lượng ước

Để giải bài toán này, chúng ta cần viết một hàm để đếm số lượng ước nguyên dương của mỗi số và so sánh chúng để xác định người thắng cuộc. Code tham khảo:

# Đọc dữ liệu từ file input
with open('BDIVMN.INP', 'r') as file:
    N, M = map(int, file.readline().split())

# Hàm đếm số lượng ước nguyên dương
def count_divisors(num):
    count = 0
    for i in range(1, int(num**0.5) + 1):
        if num % i == 0:
            count += 1
            # Nếu hai ước khác nhau thì cộng 2 vào count
            if num // i != i:
                count += 1
    return count

# Đếm số lượng ước cho N và M
count_N = count_divisors(N)
count_M = count_divisors(M)

# So sánh và xác định người thắng cuộc
winner = ""
if count_N > count_M:
    winner = "John"
    max_divisors = count_N
elif count_N < count_M:
    winner = "Jame"
    max_divisors = count_M
else:
    winner = "EQUAL"
    max_divisors = count_N  # hoặc count_M vì chúng bằng nhau

# Ghi kết quả ra file output
with open('BDIVMN.OUT', 'w') as file:
    file.write(f"{winner} {max_divisors}")

Bài 3. Số nguyên tố

Để giải bài toán này, chúng ta cần viết một hàm để đếm số lượng số nguyên tố trong mỗi đoạn [a, b] và [c, d], sau đó so sánh để xác định người thắng cuộc. Code tham khảo:

# Đọc dữ liệu từ file input
with open('CPRIMED.INP', 'r') as file:
    a, b, c, d = map(int, file.readline().split())

# Hàm kiểm tra số nguyên tố
def is_prime(num):
    if num < 2:
        return False
    for i in range(2, int(num**0.5) + 1):
        if num % i == 0:
            return False
    return True

# Hàm đếm số lượng số nguyên tố trong đoạn [start, end]
def count_primes(start, end):
    count = 0
    for num in range(start, end + 1):
        if is_prime(num):
            count += 1
    return count

# Đếm số lượng số nguyên tố cho a, b và c, d
count_John = count_primes(a, b)
count_Jame = count_primes(c, d)

# So sánh và xác định người thắng cuộc
winner = ""
if count_John > count_Jame:
    winner = "John"
    max_primes = count_John
elif count_John < count_Jame:
    winner = "Jame"
    max_primes = count_Jame
else:
    winner = "EQUAL"
    max_primes = count_John  # hoặc count_Jame vì chúng bằng nhau

# Ghi kết quả ra file output
with open('CPRIMED.OUT', 'w') as file:
    file.write(f"{winner} {max_primes}")

Bài 4. Đếm xâu

Để giải bài toán này, chúng ta cần viết một hàm để đếm số lần xuất hiện của xâu S2 trong xâu S1 và ngược lại. Sau đó, so sánh số lần xuất hiện để xác định người thắng cuộc. Code tham khảo:

# Đọc dữ liệu từ file input
with open('DCOUNTST.INP', 'r') as file:
    S1 = file.readline().strip()
    S2 = file.readline().strip()

# Hàm đếm số lần xuất hiện của xâu trong một xâu
def count_occurrences(main_str, sub_str):
    count = 0
    start = 0
    while start < len(main_str):
        start = main_str.find(sub_str, start)
        if start == -1:
            break
        count += 1
        start += 1
    return count

# Đếm số lần xuất hiện của S2 trong S1 và ngược lại
count_S1_in_S2 = count_occurrences(S2, S1)
count_S2_in_S1 = count_occurrences(S1, S2)

# So sánh và xác định người thắng cuộc
winner = ""
max_occurrences = 0

if count_S1_in_S2 > count_S2_in_S1:
    winner = "John"
    max_occurrences = count_S1_in_S2
elif count_S1_in_S2 < count_S2_in_S1:
    winner = "Jame"
    max_occurrences = count_S2_in_S1
else:
    winner = "EQUAL"
    max_occurrences = count_S1_in_S2  # hoặc count_S2_in_S1 vì chúng bằng nhau

# Ghi kết quả ra file output
with open('DCOUNTST.OUT', 'w') as file:
    file.write(f"{winner} {max_occurrences}")

Bài 5. Tặng quà

Cách 1. Đây là một bài toán có thể giải bằng phương pháp quy hoạch động. Ở đây, chúng ta có thể sử dụng phương pháp duyệt toàn bộ tập con của dãy số để kiểm tra từng trường hợp chia. Code tham khảo:

def find_min_diff_partition(arr):
    n = len(arr)
    total = sum(arr)

    # Tạo một mảng boolean để lưu trữ các cách chia quà
    dp = [[False] * (total // 2 + 1) for _ in range(n + 1)]
    
    # Khởi tạo trường hợp cơ bản
    for i in range(n + 1):
        dp[i][0] = True

    # Tìm cách chia quà
    for i in range(1, n + 1):
        for j in range(1, total // 2 + 1):
            dp[i][j] = dp[i - 1][j]
            if arr[i - 1] <= j:
                dp[i][j] |= dp[i - 1][j - arr[i - 1]]

    # Tìm độ chênh lệch nhỏ nhất
    diff = float('inf')
    for j in range(total // 2, -1, -1):
        if dp[n][j]:
            diff = total - 2 * j
            break

    return diff

def print_partition(arr, diff):
    n = len(arr)
    total = sum(arr)
    target_sum = (total - diff) // 2

    # Tìm cách chia quà
    subset = []
    i, j = n, target_sum
    while i > 0 and j > 0:
        if not dp[i - 1][j]:
            subset.append(i - 1)
            j -= arr[i - 1]
        i -= 1

    # In kết quả ra file output
    with open('EGIFTF.OUT', 'w') as file:
        file.write(f"{len(subset)} {diff}\n")
        for idx in reversed(subset):
            file.write(f"{idx + 1}\n")

# Đọc dữ liệu từ file input
with open('EGIFTF.INP', 'r') as file:
    N = int(file.readline())
    gifts = list(map(int, file.readline().split()))

# Gọi hàm để tìm và in kết quả ra file output
diff = find_min_diff_partition(gifts)
print_partition(gifts, diff)

Cách 2. Có một cách tối ưu hơn sử dụng kỹ thuật quy hoạch động với trạng thái bitset. Kỹ thuật này giúp giảm độ phức tạp thời gian và không gian so với phương pháp duyệt toàn bộ tập con của dãy số.

def find_min_diff_partition(arr):
    n = len(arr)
    total = sum(arr)

    # Khởi tạo một mảng 2D để lưu trữ trạng thái bitset
    dp = [[0] * (total // 2 + 1) for _ in range(n + 1)]

    # Tìm cách chia quà
    for i in range(1, n + 1):
        for j in range(total // 2 + 1):
            dp[i][j] = dp[i - 1][j]
            if arr[i - 1] <= j:
                dp[i][j] |= dp[i - 1][j - arr[i - 1]] | (1 << (i - 1))

    # Tìm độ chênh lệch nhỏ nhất
    diff = float('inf')
    for j in range(total // 2, -1, -1):
        if dp[n][j]:
            diff = total - 2 * j
            break

    return diff, dp

def print_partition(arr, diff, dp):
    n = len(arr)
    total = sum(arr)
    target_sum = (total - diff) // 2

    # Tìm cách chia quà
    subset = []
    i, j = n, target_sum
    while i > 0 and j > 0:
        if (dp[i][j] & (1 << (i - 1))) != 0:
            subset.append(i - 1)
            j -= arr[i - 1]
        i -= 1

    # In kết quả ra file output
    with open('EGIFTF.OUT', 'w') as file:
        file.write(f"{len(subset)} {diff}\n")
        for idx in reversed(subset):
            file.write(f"{idx + 1}\n")

# Đọc dữ liệu từ file input
with open('EGIFTF.INP', 'r') as file:
    N = int(file.readline())
    gifts = list(map(int, file.readline().split()))

# Gọi hàm để tìm và in kết quả ra file output
diff, dp = find_min_diff_partition(gifts)
print_partition(gifts, diff, dp)