Введение в Python для олимпиад: Быстрый старт

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

Анатомия олимпиадной программы: структура и типы данных

Анатомия олимпиадной программы: структура и типы данных

Представьте: вы написали гениальный алгоритм, запустили его на своем компьютере, ввели данные, получили правильный ответ. Вы отправляете код в тестирующую систему, ожидая триумфа, но мгновенно получаете вердикт Wrong Answer (неверный ответ) или Presentation Error (ошибка формата). Почему так происходит в 90% случаев у новичков?

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

Конвейер решения задачи

Любая, даже самая сложная олимпиадная программа, строится по жесткому конвейерному принципу, состоящему ровно из трех этапов.

  1. Ввод данных (Input). Тестирующая система подает данные в вашу программу в виде сплошного потока текста. Здесь нет места интерфейсам. Если вы напишете Введите число: , система воспримет эту фразу как мусор, который сломает проверку. Программа должна молча забрать текст.
  2. Обработка (Process). Текст превращается в числа, списки или графы. Алгоритм решает задачу, используя переменные и структуры данных.
  3. Вывод результата (Output). Программа печатает ответ ровно в том формате, который указан в условии. Если просят вывести одно число — вы выводите одно число. Никаких «Ответ равен 42».

Чтобы этот конвейер работал, нам нужно понимать, из каких «строительных блоков» состоит информация внутри Python.

Типы данных: из чего строятся алгоритмы

Когда тестирующая система передает вам данные, для нее это просто текст (строки). Но чтобы применять математику, текст нужно превратить в числа. Python делает работу с типами данных максимально комфортной, но требует понимания базовой четверки.

Тип данных Название Пример в коде Олимпиадная специфика
str Строка "42", "hello", "10 20" Все данные приходят в программу в виде строк. Их нельзя использовать для математики без преобразования.
int Целое число 42, -5, 0 Главное оружие Python. В отличие от C++, где целые числа ограничены размером памяти (обычно до 2109\approx 2 \cdot 10^9 или 910189 \cdot 10^{18}), в Python числа int могут быть любой длины. Число из миллиона цифр? Легко. Вы никогда не получите ошибку переполнения.
float Вещественное число 3.14, -0.001 Используется в задачах на геометрию или вероятности. Подвержен ошибкам округления (например, 0.1+0.20.30.1 + 0.2 \neq 0.3).
bool Логический тип True, False Результат проверок. Например, x>10x > 10 вернет True или False.

Ключевой инсайт: В олимпиадных задачах 95% времени вы будете работать с типом int. Бесконечная точность целых чисел в Python дает вам колоссальное преимущество перед участниками, пишущими на других языках, избавляя от необходимости писать сложную «длинную арифметику».

Строгая, но невидимая граница

Python — язык с динамической типизацией. Это значит, что при создании переменной вам не нужно заранее объявлять, что в ней будет лежать. Переменная просто принимает тип того значения, которое вы в нее положили.

Но при этом типизация в Python строгая. Язык никогда не будет втихаря угадывать, что вы имели в виду, если вы пытаетесь скрестить разные типы.

Если вы считали из системы два числа как строки и попытались их сложить: a = "2" b = "3" Результатом выражения a + b будет строка "23", а не число 55. Операция сложения для строк работает как склеивание (конкатенация).

Чтобы математика заработала, мы обязаны явно преобразовать тип с помощью функций-конструкторов: int(), float(), str().

Кульминация: собираем базовый шаблон

Давайте посмотрим, как анатомия программы и типы данных сливаются воедино на примере классической задачи «A+B». Условие: На вход подаются два целых числа, каждое на новой строке. Выведите их сумму.

# 1. Ввод данных (считываем строки и сразу превращаем в int)
a = int(input())
b = int(input())

# 2. Обработка (математика с правильным типом данных)
result = a + b

# 3. Вывод результата (молча, без лишних слов)
print(result)

Этот минималистичный код — идеальный олимпиадный боец. Он ничего не спрашивает, делает ровно то, что нужно, и отдает чистый результат.

Однако в реальных задачах данные редко подаются по одному числу на строку. Чаще всего вам придется считывать массивы чисел, записанных через пробел на одной линии. О том, как эффективно разрезать строки и мгновенно превращать их в числа, мы поговорим в следующей главе.

Эффективный ввод данных: от input() до sys.stdin.readline

Эффективный ввод данных: от input() до sys.stdin.readline

В задаче A+B мы успешно считали два числа, когда каждое из них находилось на отдельной строке. Но откройте любую реальную олимпиадную задачу, и вы увидите другой формат ввода: 10 25 — два числа записаны в одной строке через пробел.

Если мы попробуем применить наш старый подход и напишем int(input()), программа мгновенно упадет с ошибкой. Функция input() считает всю строку "10 25" целиком, а функция int() не поймет, как превратить текст с пробелом внутри в одно математическое число. Нам нужен механизм, который сначала разрежет эту строку на части, а затем превратит каждую часть в число.

Разрезаем строку: метод split()

Чтобы разделить одну большую строку на несколько маленьких, в Python используется строковый метод split(). По умолчанию он ищет любые пробельные символы (один пробел, несколько пробелов подряд или знаки табуляции) и разрезает строку по ним.

Если мы считаем строку "10 25" и применим к ней split(), то получим список из двух отдельных текстовых фрагментов: ["10", "25"].

Метод split() всегда возвращает текст (строки), даже если внутри находятся только цифры. Складывать результаты напрямую нельзя: "10" + "25" даст "1025", а не 3535.

Массовое превращение: функция map()

Теперь у нас есть несколько строк с числами, и каждую нужно обернуть в int(). Делать это вручную долго, особенно если чисел не два, а пять. Для массового применения одной функции к набору данных существует инструмент map().

Функция map() принимает два аргумента:

  1. Что применить (имя функции, например, int).
  2. К чему применить (наш разрезанный текст).

Соединим всё вместе и распакуем результат сразу в две переменные:

a, b = map(int, input().split())

Это золотой стандарт считывания данных в Python. Как он работает шаг за шагом:

  1. input() считывает строку "10 25".
  2. .split() режет её на ["10", "25"].
  3. map(int, ...) превращает их в математические числа 1010 и 2525.
  4. a, b = ... раскладывает первое число в переменную a, а второе — в b.

Скрытый враг: Time Limit Exceeded

В олимпиадном программировании каждая задача имеет жесткий лимит времени выполнения — обычно от 11 до 22 секунд. Тестирующая система (джадж) прогоняет ваш код на десятках тестов. Если данных мало, input() работает отлично.

Но представьте задачу, где на вход подается 10510^5 (сто тысяч) или даже 10610^6 строк с числами. Функция input() в Python работает довольно медленно: при каждом вызове она выполняет лишнюю работу, очищая конец строки от невидимых символов переноса. На чтении миллиона строк обычный input() может потратить больше секунды только на сам факт считывания, оставив 00 секунд на решение задачи. Вы получите вердикт TLE (Time Limit Exceeded) — время выполнения превышено, даже если ваш алгоритм был гениальным.

Турбо-режим: sys.stdin.readline

Чтобы не тратить драгоценное время на ввод, олимпиадники используют прямой доступ к потоку ввода через модуль sys.

Вместо input() мы будем использовать sys.stdin.readline(). Эта функция делает ровно то же самое — считывает одну строку, но делает это «грубо» и максимально быстро, не тратя время на обработку невидимых символов.

import sys

a, b = map(int, sys.stdin.readline().split())

Важная особенность readline: В конце каждой строки в текстовом файле стоит невидимый символ переноса строки \n (Enter). Обычный input() его заботливо удаляет. А вот sys.stdin.readline() оставляет. Если вы считаете строку с числом 1010, функция вернет "10\n".

Почему же наш код с map(int, ...) не ломается? Потому что функция int() достаточно умна: при попытке превратить "10\n" в число, она просто игнорирует перенос строки и успешно выдает 1010. Точно так же метод split() по умолчанию считает \n пробельным символом и благополучно его отбрасывает при разрезании.

Итоговый шаблон олимпиадной программы

Теперь мы можем обновить базовый конвейер программы из первой главы. Этот шаблон будет вашим верным спутником в большинстве задач:

import sys

# Считываем два числа, записанных через пробел
a, b = map(int, sys.stdin.readline().split())

# Здесь будет логика решения
результат = a + b

# Выводим ответ
print(результат)

Мы научились быстро и безопасно забирать данные у тестирующей системы. Однако в программировании числа нужно не только считывать, но и правильно обрабатывать. В Python есть специфические математические операции, которые работают не так, как в других языках — особенно когда дело касается деления.

Арифметические операции и особенности целочисленного деления в Python

Арифметические операции и особенности целочисленного деления в Python

В олимпиадном программировании вывели правильную формулу, написали быстрый ввод через sys.stdin.readline, отправляете решение — и получаете статус Wrong Answer на 42-м тесте. Частая причина кроется в одной коварной черте: вы использовали обычное деление там, где требовалось целочисленное. Python прощает многое, но работа с дробями на больших числах требует строгой дисциплины.

Базовые арифметические операции в Python интуитивны: сложение +, вычитание - и умножение * работают точно так же, как в математике. Возведение в степень записывается двумя звездочками **. Например, 10910^9 в коде выглядит как 10 ** 9. Благодаря встроенной длинной арифметике, вы можете легко вычислить 210002^{1000} — результатом будет абсолютно точное целое число типа int, занимающее столько памяти, сколько потребуется.

Но когда дело доходит до деления, Python предлагает сразу три разных инструмента.

Три вида деления

Чтобы не терять точность и не получать ошибки типов, нужно четко понимать разницу между операторами /, // и %.

Операция Символ Тип результата Описание Пример
Истинное деление / float Делит числа с образованием дробной части. 5 / 2 даст 2.5
Целочисленное деление // int Делит числа нацело, отбрасывая остаток (округление вниз). 5 // 2 даст 2
Взятие остатка % int Возвращает остаток от деления. 5 % 2 даст 1

Ловушка истинного деления (/)

Оператор / всегда возвращает вещественное число типа float, даже если числа делятся нацело. Например, 10 / 2 вернет 5.0.

Тип float в Python хранит числа с точностью около 15-17 значащих цифр. Если вы работаете с гигантскими числами (что в олимпиадах норма), использование / приведет к потере младших разрядов.

Именно поэтому в алгоритмах, где ответом должно быть целое число, нужно использовать //. Оператор // сохраняет тип int и гарантирует абсолютную математическую точность при любых размерах чисел.

Извлечение цифр числа

Связка // и % — это классический паттерн для поразрядной обработки чисел. Если мы делим число на 1010:

  • a % 10 — отсекает и возвращает последнюю цифру числа.
  • a // 10 — отбрасывает последнюю цифру, сдвигая число вправо.

Например, для числа 123123: 123 % 10 равно 33 (получили последнюю цифру). 123 // 10 равно 1212 (убрали последнюю цифру).

Отрицательные числа: сюрприз для тех, кто знает C++

В математике деление с остатком подчиняется строгому правилу: a=(a//b)×b+(a%b)a = (a // b) \times b + (a \% b). Python строго следует этому правилу, но делает это через округление вниз (к минус бесконечности). Это порождает поведение, которое часто шокирует программистов, переходящих с C++ или Java.

Давайте разделим 5-5 на 22. В C++ результат целочисленного деления отбрасывает дробную часть (округляет к нулю), поэтому 5/2-5 / 2 даст 2-2. В Python оператор // округляет вниз. Ближайшее целое число, которое меньше или равно 2.5-2.5 — это 3-3. Поэтому в Python -5 // 2 равно 3-3.

Как это влияет на остаток от деления? Подставим в формулу: 5=(3)×2+r-5 = (-3) \times 2 + r 5=6+r-5 = -6 + r Следовательно, остаток r=1r = 1.

В Python -5 % 2 вернет 11, а не 1-1. Остаток в Python всегда имеет тот же знак, что и делитель (в нашем случае делитель 22 — положительный). Это невероятно удобно для циклических сдвигов (например, при работе с массивами или криптографией), так как вам не нужно писать костыли вроде (a % m + m) % m, чтобы избавиться от отрицательных остатков.

Округление вверх без float

Частая алгоритмическая задача: у нас есть NN предметов, и мы хотим разложить их по коробкам. В одну коробку помещается KK предметов. Сколько коробок потребуется?

Очевидно, нужно разделить NN на KK и округлить вверх. Например, 1010 предметов по 33 в коробке: потребуется 44 коробки. Если написать math.ceil(N / K), мы снова рискуем попасть в ловушку потери точности типа float при очень больших NN.

В олимпиадном программировании для округления вверх используют изящный математический трюк исключительно на целых числах: Количество коробок = (N+K1)//K(N + K - 1) // K

Проверим для N=10,K=3N=10, K=3: (10+31)//3=12//3=4(10 + 3 - 1) // 3 = 12 // 3 = 4. Абсолютно точно, быстро и без вещественных чисел.

Квадратный корень для целых чисел

Иногда в задачах (особенно на проверку простоты числа или поиск делителей) нужно найти квадратный корень из большого числа, отбросив дробную часть. Возведение в дробную степень N ** 0.5 вернет float. Превращение его в целое через int(N ** 0.5) работает для небольших чисел, но на числах порядка 101810^{18} снова даст погрешность.

Для безопасного извлечения целого квадратного корня в Python есть специальная функция в модуле math:

import math
root = math.isqrt(N)

Функция math.isqrt() работает исключительно с длинной арифметикой int и возвращает точное наибольшее целое число xx, такое что x2Nx^2 \leq N.

Мы научились безопасно считать и избегать потери точности. Однако иногда задачи требуют вывести именно вещественное число с заданным количеством знаков после запятой — это мы разберем на следующем шаге.

Форматированный вывод результатов и работа с точностью

Форматированный вывод результатов и работа с точностью

Вы идеально решили геометрическую задачу. Программа вычислила ответ: 10/310 / 3. Вы отправляете код в тестирующую систему, но вместо зеленого статуса «Accepted» получаете красный «Wrong Answer». Почему? Потому что джадж ожидал ответ с точностью до тысячных: 3.333, а ваша программа с помощью обычного print() выдала 3.3333333333333335.

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

В этой статье мы научимся полностью контролировать вывод: от управления пробелами до настройки точности вещественных чисел.

Скрытые настройки функции print()

Обычно мы передаем в функцию print() переменные через запятую, и она выводит их, разделяя пробелами, а в конце невидимо нажимает «Enter» (переводит строку). Это поведение по умолчанию можно изменить с помощью двух специальных параметров: sep (от англ. separator — разделитель) и end (конец).

Параметр sep: меняем разделитель

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

a, b, c = 10, 20, 30

# По умолчанию: 10 20 30
print(a, b, c)

# Вывод через дефис: 10-20-30
print(a, b, c, sep="-")

# Слитный вывод: 102030
print(a, b, c, sep="")

Параметр end: отменяем перенос строки

По умолчанию print() всегда завершает свой вывод служебным символом \n (перенос строки). Если вам нужно, чтобы следующий вызов print() продолжил печатать текст на той же строке, измените параметр end.

# Выведет 1 и 2 на одной строке через пробел: 1 2
print(1, end=" ")
print(2)

Управление точностью: f-строки

Как мы выяснили в прошлой главе, истинное деление всегда возвращает тип float. Из-за особенностей хранения дробей в памяти компьютера, числа с плавающей точкой часто имеют длинные «хвосты» из девяток или троек.

Для форматирования таких чисел в Python используется самый современный и удобный инструмент — f-строки (форматированные строки). Чтобы превратить обычную строку в f-строку, достаточно поставить букву f перед кавычками. Внутри такой строки можно писать имена переменных или целые выражения в фигурных скобках {}.

ans = 10 / 3
# Обычный вывод: Ответ равен 3.3333333333333335
print(f"Ответ равен {ans}")

Чтобы ограничить количество знаков после запятой, внутри фигурных скобок после имени переменной нужно поставить двоеточие, затем точку, указать желаемое количество знаков и добавить букву f (от слова float).

Синтаксис: f"{переменная:.Xf}", где XX — количество знаков.

pi = 3.1415926535

print(f"{pi:.2f}") # Выведет 3.14
print(f"{pi:.4f}") # Выведет 3.1416 (обратите внимание: произошло математическое округление!)
print(f"{pi:.0f}") # Выведет 3 (без дробной части)

Если у числа не хватает знаков в дробной части, f-строка автоматически добьет их нулями. Это критически важно для задач, где в условии сказано: «выведите ответ с точностью ровно 5 знаков после запятой». Если ответ ровно 55, система ждет 5.00000, а не 5.0.

Дополнение нулями (ведущие нули)

Классический тип олимпиадных задач — вычисление времени. Допустим, вы посчитали, что событие произойдет в 9 часов 5 минут. Если вы выведете 9:5, тестирующая система засчитает ошибку, так как стандартный формат времени требует ведущих нулей: 09:05.

С помощью f-строк это решается добавлением спецификатора 0Xd, где 00 означает «заполнять нулями», XX — общую ширину числа, а d (от decimal) указывает, что это целое число.

h = 9
m = 5

# Выведет 09:05
print(f"{h:02d}:{m:02d}")

# Если число уже состоит из двух цифр, нуль не добавится
h2 = 14
print(f"{h2:02d}:{m:02d}") # Выведет 14:05

Сверхбыстрый вывод: sys.stdout.write

Во второй главе мы говорили о проблеме медленного ввода input() и заменили его на sys.stdin.readline(). С выводом ситуация аналогичная. Если ваша программа должна вывести 10510^5 строк или больше, многократный вызов print() может привести к превышению лимита времени (TLE).

Для экстремально быстрого вывода используется функция sys.stdout.write(). Однако она работает гораздо строже, чем print():

  1. Она принимает только одну строку (тип str). Вы не можете передать ей число напрямую или перечислить аргументы через запятую.
  2. Она не добавляет перенос строки \n автоматически.

Чтобы использовать sys.stdout.write() эффективно, ее нужно комбинировать с f-строками. F-строка сама превратит числа в текст и отформатирует их, а вам останется только явно добавить \n в конце.

import sys

ans1 = 42
ans2 = 3.1415

# Ошибка! write не принимает int и не принимает несколько аргументов
# sys.stdout.write(ans1, ans2)

# Правильный и очень быстрый подход:
sys.stdout.write(f"{ans1} {ans2:.2f}\n")

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

Условные конструкции и логика ветвления в задачах

Условные конструкции и логика ветвления в задачах

До этого момента наши программы напоминали продвинутые калькуляторы: они считывали данные, выполняли математические преобразования и выводили результат. Этот прямолинейный поток идеален, пока задача требует одного и того же действия для любых входных данных. Но что, если в задаче сказано: «Если число четное, разделите его на 2, иначе умножьте на 3»?

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

Базовое ветвление: if и else

Любое решение в коде опирается на тип данных bool, с которым мы познакомились в первой главе. Условие — это выражение, которое вычисляется либо в True (истина), либо в False (ложь).

Для создания развилки используется конструкция if (если) и else (иначе).

В Python границы блоков кода (что именно выполнять при выполнении условия) определяются отступами (обычно это 4 пробела), а не фигурными скобками, как в других языках. Двоеточие в конце строки с условием говорит интерпретатору: «дальше начнется блок кода, относящийся к этому условию».

Для сравнения значений используются операторы:

  • Равенство: $==$ (не путайте с одинарным $=$, которое означает присваивание).
  • Неравенство: $\neq$ (в коде пишется как !=).
  • Больше / Меньше: $>$, $<$.
  • Больше или равно / Меньше или равно: \geq, \leq (в коде >= и <=).

Рассмотрим классическую задачу: определить, является ли число четным. Мы знаем, что остаток от деления четного числа на 2 всегда равен нулю.

n = int(input())

if n % 2 == 0:
    print("Число четное")
else:
    print("Число нечетное")

Если $n = 10$, выражение $10 \% 2 == 0$ превращается в $0 == 0$, что дает True. Выполняется блок под if. Если $n = 7$, выражение дает False, и программа перепрыгивает в блок else.

Важно: Блок else не является обязательным. Если вам нужно выполнить действие только при выполнении условия, а в противном случае не делать ничего, достаточно написать только if.

Взаимоисключающие пути: elif

Часто вариантов больше, чем два. Например, число может быть положительным, отрицательным или нулем. Начинающие программисты иногда пытаются решить это серией независимых if:

x = int(input())

if x > 0:
    print("Положительное")
if x < 0:
    print("Отрицательное")
if x == 0:
    print("Ноль")

Этот код работает, но он неэффективен. Программа всегда проверит все три условия, даже если уже первое оказалось истинным. В олимпиадных задачах с жесткими лимитами времени (TLE) лишние проверки на миллионах операций могут стоить победы.

Для создания цепочки взаимоисключающих проверок используется elif (сокращение от else if).

Независимые условия (if-if) Взаимоисключающие условия (if-elif-else)
Проверяются все условия по очереди сверху вниз. Проверка останавливается, как только найдено первое истинное условие.
Несколько блоков могут выполниться, если данные подходят под разные условия. Выполнится строго один блок (или ни одного, если нет else).
Подходит, когда нужно применить несколько независимых фильтров. Подходит для классификации одного объекта по категориям.

Правильный подход к задаче о знаке числа:

if x > 0:
    print("Положительное")
elif x < 0:
    print("Отрицательное")
else:
    print("Ноль")

Сложные условия: and, or, not

В реальных задачах условие редко состоит из одного сравнения. Например, чтобы точка с координатами (x,y)(x, y) находилась в первой координатной четверти, должны выполняться два условия одновременно: $x > 0$ и $y > 0$.

Для объединения условий используются логические операторы:

  1. and (Логическое И): Возвращает True, только если все части условия истинны.
  2. or (Логическое ИЛИ): Возвращает True, если хотя бы одна часть условия истинна.
  3. not (Логическое НЕ): Инвертирует результат (превращает True в False и наоборот).

Пример проверки попадания точки в первую или третью четверть:

if (x > 0 and y > 0) or (x < 0 and y < 0):
    print("Точка в I или III четверти")

Ленивое вычисление (Short-circuit evaluation)

Python оптимизирует проверку сложных условий. Если в выражении A and B условие A оказалось False, Python даже не будет вычислять B, потому что результат всего выражения уже гарантированно будет False. Аналогично для A or B: если A равно True, вычисление B пропускается. Это свойство часто используют, чтобы избежать ошибок, например, деления на ноль: if y != 0 and x / y > 5:.

Олимпиадные хитрости Python

Python предлагает несколько синтаксических конструкций, которые делают код короче и читаемее, что критически важно на соревнованиях.

1. Цепочки сравнений

В большинстве языков программирования проверка того, что число $x$ находится в диапазоне от 10 до 20, выглядит так: x >= 10 and x <= 20. Python позволяет писать математически естественно:

if 10 <= x <= 20:
    print("В диапазоне")

Это не только короче, но и работает немного быстрее, так как переменная $x$ вычисляется ровно один раз.

2. Тернарный оператор (Условное выражение)

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

if a > b:
    maximum = a
else:
    maximum = b

Можно использовать однострочный тернарный оператор: значение_если_True if условие else значение_если_False.

maximum = a if a > b else b

Это особенно мощно работает в связке с f-строками (из предыдущей главы) для мгновенного форматированного вывода ответа:

# Выводим YES для четных и NO для нечетных без лишних переменных
print(f"Ответ: {'YES' if n % 2 == 0 else 'NO'}")

Синтез: Собираем всё вместе

Рассмотрим классическую базовую задачу из контестов: «Шахматная ладья». Условие: Даны координаты двух клеток на шахматной доске: (x1,y1)(x_1, y_1) и (x2,y2)(x_2, y_2). Координаты — целые числа от 1 до 8. Требуется определить, может ли ладья попасть из первой клетки во вторую за один ход. Ладья ходит только по горизонтали или вертикали.

Мы применим быстрый ввод данных, распаковку и логику ветвления:

import sys

# Считываем 4 числа с одной строки
x1, y1, x2, y2 = map(int, sys.stdin.readline().split())

# Ладья бьет клетку, если они находятся на одной вертикали (x1 == x2)
# ИЛИ на одной горизонтали (y1 == y2)
if x1 == x2 or y1 == y2:
    sys.stdout.write("YES\n")
else:
    sys.stdout.write("NO\n")

Теперь наша программа умеет анализировать данные и выбирать сценарий действий. Однако пока она делает это только один раз за запуск. В олимпиадных задачах часто просят обработать сотни таких запросов подряд. Как заставить код повторять нужные действия, мы разберем на следующем шаге.

Организация циклов для обработки многострочного ввода

Организация циклов для обработки многострочного ввода

Представьте, что вы написали идеальную логику для проверки, является ли число четным. Код работает безупречно. Но тестирующая система отправляет в вашу программу не одно число, а 10510^5 чисел подряд. Вы же не будете копировать строку с чтением данных сто тысяч раз? Олимпиадные задачи редко ограничиваются единичным набором данных. Чтобы программа могла обрабатывать непрерывный поток информации, нам нужен механизм многократного повторения действий — циклы.

В олимпиадном программировании существует три главных паттерна ввода данных. Выбор цикла зависит от того, как именно тестирующая система сообщает вам об окончании тестов.

Паттерн 1: Известное количество тестов (цикл for)

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

Когда количество повторений известно заранее, используется цикл for в связке с функцией range(). Функция range(N) генерирует последовательность от 00 до N1N - 1, заставляя цикл выполниться ровно NN раз.

import sys

# Считываем количество тестов
n = int(sys.stdin.readline())

# Повторяем блок кода N раз
for _ in range(n):
    # Внутри цикла считываем и сразу обрабатываем данные
    a, b = map(int, sys.stdin.readline().split())
    ans = a + b
    sys.stdout.write(f"{ans}\n")

Обратите внимание на переменную _ (нижнее подчёркивание). В Python это общепринятое соглашение: если нам нужно просто повторить действие NN раз, но сам номер текущего шага (0, 1, 2...) внутри цикла не используется, мы называем переменную-счетчик нижним подчёркиванием. Это подсказывает и вам, и другим программистам: переменная нужна только для синтаксиса.

Паттерн 2: Остановка по маркеру (цикл while)

Иногда количество тестов неизвестно, но в условии сказано: «Ввод завершается, когда поступает число 0» (или любое другое специфичное значение, например, отрицательное число).

Здесь for не поможет, так как мы не знаем, сколько раз сработает цикл. Нужен цикл while, который выполняет код до тех пор, пока истинно заданное условие.

Самый элегантный способ реализовать это в Python — использовать бесконечный цикл while True и прерывать его изнутри с помощью оператора break, как только встретится маркер остановки.

import sys

while True:
    # 1. Считываем данные
    x = int(sys.stdin.readline())

    # 2. Проверяем условие выхода
    if x == 0:
        break  # Немедленно прерывает цикл

    # 3. Обрабатываем данные (если не сработал break)
    if x % 2 == 0:
        sys.stdout.write("Even\n")
    else:
        sys.stdout.write("Odd\n")

Почему while True лучше? Альтернативный подход требует считать данные один раз до цикла, а затем дублировать строку чтения внутри цикла в самом конце. Использование while True с break избавляет код от дублирования и делает логику линейной: прочитал \rightarrow проверил \rightarrow обработал.

Паттерн 3: Чтение до конца файла (EOF)

Самый коварный для новичков формат: в условии просто сказано «На вход подается последовательность чисел», и нет ни NN, ни маркера остановки. Тестирующая система просто вливает в вашу программу поток байтов, а затем закрывает канал передачи. Это называется EOF (End of File).

Если в такой ситуации использовать while True, программа попытается считать данные, которых больше нет, и либо упадет с ошибкой, либо зависнет, получив вердикт TLE (Time Limit Exceeded).

Здесь на помощь приходит особенность модуля sys. Объект sys.stdin — это не просто инструмент для вызова readline(). Это итерируемый объект, который сам по себе представляет весь поток ввода. Если запустить по нему цикл for, он будет автоматически считывать по одной строке за раз и сам корректно остановится, когда данные закончатся.

import sys

# Цикл автоматически берет по одной строке из ввода
# и завершается, когда наступает EOF
for line in sys.stdin:
    # line содержит строку вместе с символом \n
    a, b = map(int, line.split())
    sys.stdout.write(f"{a * b}\n")

Это самый быстрый и лаконичный способ обработки неизвестного количества строк в Python.

Синтез: Полный конвейер обработки

Теперь мы можем собрать воедино знания из предыдущих глав. У нас есть строгий ввод (sys.stdin), математика, логика ветвления (if/else), быстрый вывод (sys.stdout.write) и циклы для обработки потока.

Сравним два подхода к решению задачи: «Дано NN запросов. В каждом запросе два числа AA и BB. Вывести наибольшее из них».

Подход Код Проблемы
Медленный (для новичков) n = int(input())<br>for i in range(n):<br>    a, b = map(int, input().split())<br>    if a > b:<br>        print(a)<br>    else:<br>        print(b) Медленный input(), медленный print(), создание лишней переменной i. На 10510^5 строках может получить TLE.
Олимпиадный (оптимизированный) import sys<br>n = int(sys.stdin.readline())<br>for _ in range(n):<br>    a, b = map(int, sys.stdin.readline().split())<br>    ans = a if a > b else b<br>    sys.stdout.write(f"{ans}\n") Использует быстрый ввод/вывод, тернарный оператор для краткости, не засоряет память лишними переменными.

Обратите внимание на важнейший принцип: мы считываем один запрос, обрабатываем его и сразу выводим ответ, после чего переходим к следующему шагу цикла. Нам не нужно запоминать все входные данные или копить все ответы, чтобы вывести их разом в конце. Тестирующая система читает ваш вывод асинхронно, поэтому потоковая обработка «на лету» — это золотой стандарт экономии оперативной памяти.

Однако, существуют задачи, где решение зависит от всей совокупности данных (например, нужно отсортировать все числа). Для таких случаев потоковой обработки недостаточно, и данные придется сохранять в память программы. Как именно это делать, мы разберем далее.