Источник задачи: региональный этап Олимпиады им. М.В. Келдыша, 2025, задача B.
Что дано
Даны крыш. Каждая крыша — прямоугольник размера с левым нижним углом в начале координат . На каждой крыше уже положены два листа покрытия размером — они не пересекаются друг с другом и хотя бы частично попадают на крышу. Листы нельзя поворачивать (ориентация по оси X, по оси Y фиксирована).
Для каждой крыши нужно определить: можно ли добавить произвольное число таких же листов (без поворотов, без взаимных пересечений, допускается выход листов за границы крыши) так, чтобы вся крыша оказалась полностью покрыта. Ответ — «Yes» или «No».
Ограничения
- Координаты левого нижнего угла каждого листа: , (гарантируют хотя бы частичное пересечение листа с крышей).
Формат ввода
Первая строка — число крыш . Далее для каждой крыши две строки: первая содержит четыре числа , , , ; вторая — четыре числа , , , (координаты левых нижних углов двух уже положенных листов).
Формат вывода
Для каждой крыши вывести отдельной строкой Yes, если покрытие достижимо, или No иначе.
Примеры
Вход:
3
6 5 2 3
-1 -2 5 4
4 4 2 2
0 0 3 1
2 2 1 1
0 0 1 1
Вывод:
Yes
No
Yes
Разберём пример руками
Первая крыша из примера:
6 5 2 3
-1 -2 5 4
. Лист №1 в точке , лист №2 в точке .
Лист №1 покрывает прямоугольник — торчит снизу-слева, заходит на крышу в .
Лист №2 покрывает — торчит сверху-справа.
Интуитивно: эти два листа «по диагонали», между ними есть большая непокрытая область. Можем ли мы её замостить одинаковыми листами без поворотов?
Попробуем ставить листы «слева направо, снизу вверх» с шагом . Лист №1 стоит на . Если мы шагнём вправо , потом , потом — это столбец из . Лист №2 имеет — попадает в нашу сетку! Значит по оси X листы сочетаются: , делится на .
А по оси Y? Лист №1 стоит в , шагаем вверх: . Лист №2 имеет — тоже попадает: , делится на . Значит можно мостить хоть по строкам, хоть по столбцам.
Ответ: Yes.
Вторая крыша:
4 4 2 2
0 0 3 1
Шаг , листы по X в . Лист №2 имеет — не попадает: , не делится на 2.
Шаг , листы по Y в . Лист №2 имеет — не попадает: , не делится на 2.
Ни по строкам, ни по столбцам замостить нельзя. Ответ: No.
Третья крыша:
2 2 1 1
0 0 1 1
— каждая клетка отдельный «лист». делится на . Yes.
Идея решения
Ключевое наблюдение: любое корректное замощение крыши листами одного размера без поворотов (с возможным выходом за границы) обязательно разбивается на регулярную сетку — листы выстраиваются либо строго по строкам (одинаковые -координаты в пределах одной строки), либо строго по столбцам (одинаковые ).
Почему так?
Без ограничения общности допустим, что где-то два соседних листа стоят «в одной строке»: одинаковый , отличаются на . Тогда лист слева от второго прикладывается к первому справа впритык — другого способа нет, потому что листы не пересекаются и не поворачиваются. Значит вся эта строка заполняется листами с шагом по X.
А следующая строка? Она покрывает -диапазон . Лист из следующей строки должен касаться текущей строки (иначе между строками щель). Касание — сторона к стороне, значит все листы следующей строки выравнены по .
И дальше — всё тянется: вся крыша покрывается рядами с шагом по Y, внутри ряда — с шагом по X. При этом -координаты всех листов одной строки отличаются на , а листов между соседними строками — совпадают (или тоже смещены на , но в рамках одной глобальной сетки).
Аналогично, если два соседних листа «в одном столбце» (одинаковый ) — замощение идёт по столбцам.
Вывод: в корректном замощении все листы лежат на узлах сетки для некоторой стартовой точки .
Что это значит для двух заданных листов
Если замощение идёт «по строкам» (значит все листы на разных , но кратны от стартового), то оба заданных листа должны быть на одном горизонтальном уровне, то есть . Тогда отличаются на кратное : .
Если «по столбцам» — наоборот: и .
Но есть ещё общий случай. Если и , то можно попробовать любое из двух — достаточно выполнения любого из условий:
- и — значит листы совместимы с замощением «по строкам», и они окажутся на разных -координатах одной и той же строки (либо разных строк, но в одной сетке).
- и — аналогично для столбцов.
Если же , то они в одном столбце — работает только «по столбцам»: нужно . Но в этом случае «первое» условие () ложно, поэтому проверка просто сводится ко второму.
Аналогично, если — проверяется только первое условие.
Итоговая формула
Yes тогда и только тогда, когда выполнено хотя бы одно из:
- ;
- .
Иначе — No.
Решение: псевдокод
читать n
повторить n раз:
читать w, h, a, b
читать x1, y1, x2, y2
cols_ok = (x1 != x2) и ((x2 - x1) mod a == 0)
rows_ok = (y1 != y2) и ((y2 - y1) mod b == 0)
если cols_ok или rows_ok:
вывести "Yes"
иначе:
вывести "No"
на крышу. Всего .
Код решения
Переключи вкладку, чтобы посмотреть второй язык. В Python про переполнение int думать не надо — он неограниченной точности. В C++ обязателен long long: координаты до , разность — до , в int не помещается.
Переменные в решении не используются напрямую — размер крыши не влияет на условие замощения (интуиция: мы работаем с бесконечной сеткой и проверяем, попадают ли оба листа в одну сетку).
Проверим на примере из условия
Входные данные:
3
6 5 2 3
-1 -2 5 4
4 4 2 2
0 0 3 1
2 2 1 1
0 0 1 1
Крыша 1
, листы и .
- , . cols_ok = true.
- , . rows_ok = true.
- Ответ: Yes ✓
Крыша 2
, листы и .
- , . cols_ok = false.
- , . rows_ok = false.
- Ответ: No ✓
Крыша 3
, листы и .
- . cols_ok = true (плюс ).
- Ответ: Yes ✓
Всё сходится.
Крайние случаи
1. Листы в одном столбце ()
Тогда cols_ok сразу false (из-за условия). Остаётся проверить rows_ok: (всегда так, т.к. листы не пересекаются) и .
2. Листы в одной строке ()
Симметрично: rows_ok false, проверяется только cols_ok.
3. Отрицательные координаты
Листы могут стоять в и т.д. Модульная арифметика по отрицательным числам в Python работает корректно ((-3) % 2 == 1), в C++ нужно учесть, что % может вернуть отрицательное. Но в задаче не гарантируется, поэтому может быть отрицательным. Однако для проверки «делится на » важен только остаток, и в C++ (-6) % 2 == 0 корректно — никаких проблем с отрицательными не возникает для нулевой проверки.
Но если вдруг решаешь на языке, где % по-другому работает, — используй abs(x2 - x1) % a == 0, это всегда корректно.
4. Максимальные значения
, координаты до по модулю. Разность координат — до , в int не помещается. Обязательно long long в C++.
5. крыш
операций — мгновенно. Никаких оптимизаций ввода-вывода сверх обычных не требуется.
Типичные ошибки
-
Пропустить проверку / . Если не добавить эту проверку, при получишь
cols_ok = true(так как ), хотя по замощению «по строкам» два листа в одной точке невозможны. В таком случае правильно замостить только по столбцам — иrows_okдолжна контролировать этот путь отдельно. -
Попытаться использовать . Возникает ощущение, что размер крыши влияет. На самом деле — нет: задача сведена к бесконечной сетке, а крыша просто вырезает из неё нужный кусок. Ответ зависит только от того, совместимы ли два данных листа с какой-то сеткой шага .
-
intв C++. Разность координат — до . В 32-битномintпереполнение. -
Неверная реализация
%для отрицательных. В Python%всегда неотрицателен, в C++ может быть отрицательным, но для проверки «равно 0» это не имеет значения. Проблема возникла бы только если сравнивать остаток с конкретным положительным числом — нам не нужно. -
Попытка перебрать все возможные листы. Размер крыши до — перебор невозможен. Решение концептуальное, не симуляционное.
Сложность
- Время: на крышу, всего.
- Память: .
Что ещё полезно потренировать
Похожие задачи — на конструктивные алгоритмы и регулярные замощения:
- Codeforces 1466C «Canine poetry» — конструктивное преобразование строки, ключ — увидеть инвариант.
- Codeforces 1399D «Binary String To Subsequences» — распределение 0/1 на подпоследовательности, жадное построение.
- Codeforces 1593B «Make it Divisible by 25» — математическая жадность + перебор случаев.
- acmp.ru задача 266 «Парикмахерская» — жадное распределение по слотам.
Общий мотив: увидеть, что «ответ существует тогда и только тогда, когда выполнено простое арифметическое условие». Чаще всего — делимость, остатки, порядок.
Итого
- Идея: любое корректное замощение идёт «по строкам» или «по столбцам» — листы образуют регулярную сетку.
- Условие: .
- Сложность: .
- Ловушки: забыть проверку / ;
intвместоlong longв C++; попытаться использовать (они не нужны).