Какие задания требуют кода

В КЕГЭ задания на программирование сосредоточены в двух местах. В первой части это задание 16 — трассировка рекурсивной функции: нужно вычислить возвращаемое значение или подсчитать количество вызовов. Задания 24–25 требуют обработать файл с данными и записать числовой ответ. Во второй части — задания 26–27: самостоятельно написать программу для алгоритмической задачи. В КЕГЭ ответ на любое задание вводится в бланк как краткий (число или последовательность), заданий с развёрнутым ответом нет — но именно для 26 и 27 нужно написать полноценную программу, и их решение отличает высокобалльников.

Из языков доступны Python, C++ и Pascal. Большинство сдающих выбирают Python — он короче, читаемее, и ФИПИ включает его в примеры решений демоверсий.

Минимально необходимый Python

Для КЕГЭ не нужна продвинутая экспертиза. Достаточно освоить около десяти конструкций и знать их без запинки.

Ввод и вывод:

n = int(input())                        # одно число
a, b = map(int, input().split())        # два числа через пробел
nums = list(map(int, input().split()))  # список чисел

Списки и базовые операции:

nums.sort()              # сортировка на месте
nums.sort(reverse=True)  # по убыванию
m = max(nums)            # максимум
total = sum(nums)        # сумма

Строки:

s = input().strip()
words = s.split()   # разбить по пробелу
s.count('a')        # число вхождений символа
s.lower()           # нижний регистр

Счётчик — незаменимый паттерн:

from collections import Counter
cnt = Counter(words)
best = cnt.most_common(1)[0]  # самое частое слово и число вхождений

Если эти блоки у вас «отскакивают от зубов» — уже можете браться за задания 24–25.

Обработка файла (задания 24–25)

В 2026 году файлы для КЕГЭ поставляются в форматах открытого ПО. На практике вы получаете текстовый файл с числами или словами — и нужно найти количество элементов, удовлетворяющих условию, максимум/минимум или подсчитать частоты.

Типичная структура решения:

result = 0
with open('input.txt', 'r', encoding='utf-8') as f:
    for line in f:
        for token in line.split():
            x = int(token)
            if x % 2 == 0 and x > 100:
                result += 1
print(result)

Три вещи, которые спасают баллы:

1. Всегда указывайте encoding='utf-8' (или 'cp1251' — читайте условие задачи).

2. Если нужен максимум — инициализируйте float('-inf'), не нулём: в файле могут быть отрицательные числа.

3. Пустые строки не ломают line.split() — метод вернёт пустой список, цикл по токенам просто не выполнится.

Задание с таблицей (числа в строках и столбцах):

table = []
with open('data.txt', 'r', encoding='utf-8') as f:
    for line in f:
        row = list(map(int, line.split()))
        if row:
            table.append(row)

row_maxes = [max(row) for row in table]
print(max(row_maxes))
Check yourself
В файле хранятся числа от −1000 до 1000. Нужно найти наибольшее чётное число. Программист написал `max_val = 0` и в конце выводит `max_val`. Найдите баг.
Quick recall
Почему при поиске максимума в файле нужно инициализировать max_val = float('-inf')?

Рекурсия: задание 16

Здесь не нужно писать код — нужно трассировать: отследить, что вернёт функция. Типичный пример:

def f(n):
    if n <= 1:
        return n
    return f(n - 1) + f(n - 2)

При $n = 4$: $f(2) = f(1) + f(0) = 1$; $f(3) = f(2) + f(1) = 2$; $f(4) = f(3) + f(2) = 3$.

Ключевой приём — дерево вызовов: рисуйте каждый вызов как узел, ветви — аргументы дочерних вызовов. Для небольших $n$ (обычно до $6$$8$) дерево строится за пару минут.

Часто встречается другой вид — с делением пополам:

def g(n):
    if n == 0:
        return 1
    if n % 2 == 0:
        return g(n // 2) * 2
    else:
        return g(n - 1) + 3

Трассировка $g(6)$: $6$ чётное $\Rightarrow$ $g(3) \cdot 2$; $3$ нечётное $\Rightarrow$ $g(2) + 3$; $2$ чётное $\Rightarrow$ $g(1) \cdot 2$; $1$ нечётное $\Rightarrow$ $g(0) + 3 = 4$. Откат: $g(2) = 8$, $g(3) = 11$, $g(6) = 22$.

flowchart TD A["g(6): чётное"] -->|"вызов g(3) * 2"| B["g(3): нечётное"] B -->|"вызов g(2) + 3"| C["g(2): чётное"] C -->|"вызов g(1) * 2"| D["g(1): нечётное"] D -->|"вызов g(0) + 3"| E["g(0) = 1"] E -->|"g(1) = 1+3"| F["g(1) = 4"] F -->|"g(2) = 4*2"| G["g(2) = 8"] G -->|"g(3) = 8+3"| H["g(3) = 11"] H -->|"g(6) = 11*2"| I["g(6) = 22"]
flowchart TD
    A["g(6): чётное"] -->|"вызов g(3) * 2"| B["g(3): нечётное"]
    B -->|"вызов g(2) + 3"| C["g(2): чётное"]
    C -->|"вызов g(1) * 2"| D["g(1): нечётное"]
    D -->|"вызов g(0) + 3"| E["g(0) = 1"]
    E -->|"g(1) = 1+3"| F["g(1) = 4"]
    F -->|"g(2) = 4*2"| G["g(2) = 8"]
    G -->|"g(3) = 8+3"| H["g(3) = 11"]
    H -->|"g(6) = 11*2"| I["g(6) = 22"]
Трассировка g(6): цепочка вызовов вниз, возврат значений обратно
Check yourself
Сколько раз будет вызвана функция `g` при вычислении `g(6)` (включая сам вызов `g(6)`)? Используйте диаграмму.
Quick recall
Какой приём используется для трассировки рекурсивной функции в задании 16?

Динамическое программирование

В заданиях второй части встречаются задачи, где наивная рекурсия с разветвлением работает слишком медленно — одни и те же подзадачи вычисляются по многу раз. Два способа ускорить.

Мемоизация через декоратор:

from functools import lru_cache

@lru_cache(maxsize=None)
def f(n):
    if n <= 1:
        return n
    return f(n - 1) + f(n - 2)

Итеративный DP — предпочтительнее при больших $n$, не вызывает RecursionError:

def f_dp(n):
    if n <= 1:
        return n
    dp = [0] * (n + 1)
    dp[1] = 1
    for i in range(2, n + 1):
        dp[i] = dp[i - 1] + dp[i - 2]
    return dp[n]

Типичная DP-задача — подсчитать число способов набрать сумму $S$ монетами заданных номиналов:

coins = [1, 2, 5]
S = 10
dp = [0] * (S + 1)
dp[0] = 1  # один способ набрать 0 — ничего не брать
for coin in coins:
    for s in range(coin, S + 1):
        dp[s] += dp[s - coin]
print(dp[S])  # 10
Quick recall
Когда в рекурсивном DP-решении нужна мемоизация?

Задания второй части: 26 и 27

Задание 26 — обычно «напишите программу, которая…» с конкретным алгоритмическим условием. Проверяющий смотрит на результат, а не на стиль кода.

Задание 27 — сложнее: данные большие (файл с тысячами строк), поэтому решение с вложенными циклами за $O(n^2)$ может не уложиться в ограничение по времени.

Стратегия для второй части:

1. Прочитайте задание дважды. Выделите: что дано, что найти, в каком формате вывод.

2. Набросайте структуру на бумаге: какие переменные, какой цикл.

3. Напишите код маленькими кусками и проверяйте на примере из условия прямо во время написания.

4. Для задания 27 сразу думайте об эффективности: dict/Counter вместо вложенных for, сортировка за $O(n \log n)$.

Типичный паттерн задания 27:

# Найти слово, встречающееся в наибольшем числе строк
from collections import defaultdict

word_lines = defaultdict(set)
with open('text.txt', 'r', encoding='utf-8') as f:
    for i, line in enumerate(f):
        for word in line.lower().split():
            word_lines[word].add(i)

best = max(word_lines, key=lambda w: len(word_lines[w]))
print(best, len(word_lines[best]))
Check yourself
В DP-задаче о монетах вы пишете `dp[0] = 1`. В чём смысл этой инициализации и что сломается, если написать `dp[0] = 0`?

Типичные ошибки

  • RecursionError — Python ограничивает глубину рекурсии примерно $1000$ вызовами. При больших $n$ в задании 27 используйте итеративный DP.
  • Целочисленное деление: / в Python 3 возвращает float. Для целых чисел используйте //.
  • Индексация с нуля: первый элемент — nums[0], последний — nums[-1]. Ошибка «на единицу» — самая частая в DP-задачах.
  • Инициализация максимума нулём: если в данных есть отрицательные числа, max_val = 0 даст неверный ответ. Используйте float('-inf').
  • Формат вывода: убедитесь, что print() выводит именно то, что требует задание — одно число, список через пробел или каждое значение на новой строке.

См. также