← Вернуться в блог

Замощение прямоугольника одинаковыми листами за O(1) — задача «Хорошее начало» РАЗБОР

  • 1500
  • constructive
  • math
  • geometry
  • implementation

Источник задачи: региональный этап Олимпиады им. М.В. Келдыша, 2025, задача B.

Что дано

Даны nn крыш. Каждая крыша — прямоугольник размера w×hw \times h с левым нижним углом в начале координат (0,0)(0, 0). На каждой крыше уже положены два листа покрытия размером a×ba \times b — они не пересекаются друг с другом и хотя бы частично попадают на крышу. Листы нельзя поворачивать (ориентация aa по оси X, bb по оси Y фиксирована).

Для каждой крыши нужно определить: можно ли добавить произвольное число таких же листов a×ba \times b (без поворотов, без взаимных пересечений, допускается выход листов за границы крыши) так, чтобы вся крыша оказалась полностью покрыта. Ответ — «Yes» или «No».

Ограничения

  • 1n1041 \leq n \leq 10^4
  • 1w,h,a,b1091 \leq w, h, a, b \leq 10^9
  • Координаты левого нижнего угла каждого листа: a+1xw1-a + 1 \leq x \leq w - 1, b+1yh1-b + 1 \leq y \leq h - 1 (гарантируют хотя бы частичное пересечение листа с крышей).

Формат ввода

Первая строка — число крыш nn. Далее для каждой крыши две строки: первая содержит четыре числа ww, hh, aa, bb; вторая — четыре числа x1x_1, y1y_1, x2x_2, y2y_2 (координаты левых нижних углов двух уже положенных листов).

Формат вывода

Для каждой крыши вывести отдельной строкой 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

w=6,h=5,a=2,b=3w = 6, h = 5, a = 2, b = 3. Лист №1 в точке (1,2)(-1, -2), лист №2 в точке (5,4)(5, 4).

Лист №1 покрывает прямоугольник [1,1)×[2,1)[-1, 1) \times [-2, 1) — торчит снизу-слева, заходит на крышу в [0,1)×[0,1)[0, 1) \times [0, 1).

Лист №2 покрывает [5,7)×[4,7)[5, 7) \times [4, 7) — торчит сверху-справа.

Интуитивно: эти два листа «по диагонали», между ними есть большая непокрытая область. Можем ли мы её замостить одинаковыми листами 2×32 \times 3 без поворотов?

Попробуем ставить листы «слева направо, снизу вверх» с шагом (a,b)=(2,3)(a, b) = (2, 3). Лист №1 стоит на (1,2)(-1, -2). Если мы шагнём вправо (1+2,2)=(1,2)(-1+2, -2) = (1, -2), потом (3,2)(3, -2), потом (5,2)(5, -2) — это столбец из x{1,1,3,5}x \in \{-1, 1, 3, 5\}. Лист №2 имеет x=5x = 5попадает в нашу сетку! Значит по оси X листы сочетаются: 5(1)=65 - (-1) = 6, делится на a=2a = 2.

А по оси Y? Лист №1 стоит в y=2y = -2, шагаем вверх: y{2,1,4}y \in \{-2, 1, 4\}. Лист №2 имеет y=4y = 4тоже попадает: 4(2)=64 - (-2) = 6, делится на b=3b = 3. Значит можно мостить хоть по строкам, хоть по столбцам.

Ответ: Yes.

Вторая крыша:

4 4 2 2
0 0 3 1

Шаг a=2a = 2, листы по X в {0,2}\{0, 2\}. Лист №2 имеет x=3x = 3не попадает: 30=33 - 0 = 3, не делится на 2.

Шаг b=2b = 2, листы по Y в {0,2}\{0, 2\}. Лист №2 имеет y=1y = 1не попадает: 10=11 - 0 = 1, не делится на 2.

Ни по строкам, ни по столбцам замостить нельзя. Ответ: No.

Третья крыша:

2 2 1 1
0 0 1 1

a=b=1a = b = 1 — каждая клетка отдельный «лист». 10=11 - 0 = 1 делится на 11. Yes.


Идея решения

Ключевое наблюдение: любое корректное замощение крыши листами одного размера без поворотов (с возможным выходом за границы) обязательно разбивается на регулярную сетку — листы выстраиваются либо строго по строкам (одинаковые yy-координаты в пределах одной строки), либо строго по столбцам (одинаковые xx).

Почему так?

Без ограничения общности допустим, что где-то два соседних листа стоят «в одной строке»: одинаковый yy, xx отличаются на aa. Тогда лист слева от второго прикладывается к первому справа впритык — другого способа нет, потому что листы не пересекаются и не поворачиваются. Значит вся эта строка заполняется листами с шагом aa по X.

А следующая строка? Она покрывает yy-диапазон [y0+b,y0+2b)[y_0 + b, y_0 + 2b). Лист из следующей строки должен касаться текущей строки (иначе между строками щель). Касание — сторона к стороне, значит все листы следующей строки выравнены по y=y0+by = y_0 + b.

И дальше — всё тянется: вся крыша покрывается рядами с шагом bb по Y, внутри ряда — с шагом aa по X. При этом xx-координаты всех листов одной строки отличаются на aa, а листов между соседними строками — совпадают (или тоже смещены на aa, но в рамках одной глобальной сетки).

Аналогично, если два соседних листа «в одном столбце» (одинаковый xx) — замощение идёт по столбцам.

Вывод: в корректном замощении все листы лежат на узлах сетки {(x0+ia,y0+jb):i,jZ}\{(x_0 + i \cdot a, y_0 + j \cdot b) : i, j \in \mathbb{Z}\} для некоторой стартовой точки (x0,y0)(x_0, y_0).

Что это значит для двух заданных листов

Если замощение идёт «по строкам» (значит все листы на разных xx, но yy кратны bb от стартового), то оба заданных листа должны быть на одном горизонтальном уровне, то есть y1=y2y_1 = y_2. Тогда xx отличаются на кратное aa: (x2x1)moda=0(x_2 - x_1) \bmod a = 0.

Если «по столбцам» — наоборот: x1=x2x_1 = x_2 и (y2y1)modb=0(y_2 - y_1) \bmod b = 0.

Но есть ещё общий случай. Если x1x2x_1 \neq x_2 и y1y2y_1 \neq y_2, то можно попробовать любое из двух — достаточно выполнения любого из условий:

  • x1x2x_1 \neq x_2 и (x2x1)moda=0(x_2 - x_1) \bmod a = 0 — значит листы совместимы с замощением «по строкам», и они окажутся на разных xx-координатах одной и той же строки (либо разных строк, но в одной сетке).
  • y1y2y_1 \neq y_2 и (y2y1)modb=0(y_2 - y_1) \bmod b = 0 — аналогично для столбцов.

Если же x1=x2x_1 = x_2, то они в одном столбце — работает только «по столбцам»: нужно (y2y1)modb=0(y_2 - y_1) \bmod b = 0. Но в этом случае «первое» условие (x1x2x_1 \neq x_2) ложно, поэтому проверка просто сводится ко второму.

Аналогично, если y1=y2y_1 = y_2 — проверяется только первое условие.

Итоговая формула

Yes тогда и только тогда, когда выполнено хотя бы одно из:

  • x1x2    (x2x1)moda=0x_1 \neq x_2 \; \land \; (x_2 - x_1) \bmod a = 0;
  • y1y2    (y2y1)modb=0y_1 \neq y_2 \; \land \; (y_2 - y_1) \bmod b = 0.

Иначе — 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"

O(1)O(1) на крышу. Всего O(n)O(n).


Код решения

Переключи вкладку, чтобы посмотреть второй язык. В Python про переполнение int думать не надо — он неограниченной точности. В C++ обязателен long long: координаты до 10910^9, разность x2x1x_2 - x_1 — до 21092 \cdot 10^9, в int не помещается.

Переменные w,hw, h в решении не используются напрямую — размер крыши не влияет на условие замощения (интуиция: мы работаем с бесконечной сеткой и проверяем, попадают ли оба листа в одну сетку).


Проверим на примере из условия

Входные данные:

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

w=6,h=5,a=2,b=3w=6, h=5, a=2, b=3, листы (1,2)(-1, -2) и (5,4)(5, 4).

  • x1=1x2=5x_1 = -1 \neq x_2 = 5, (5(1))mod2=6mod2=0(5 - (-1)) \bmod 2 = 6 \bmod 2 = 0. cols_ok = true.
  • y1=2y2=4y_1 = -2 \neq y_2 = 4, (4(2))mod3=6mod3=0(4 - (-2)) \bmod 3 = 6 \bmod 3 = 0. rows_ok = true.
  • Ответ: Yes

Крыша 2

w=4,h=4,a=2,b=2w=4, h=4, a=2, b=2, листы (0,0)(0, 0) и (3,1)(3, 1).

  • x1=0x2=3x_1 = 0 \neq x_2 = 3, (30)mod2=10(3 - 0) \bmod 2 = 1 \neq 0. cols_ok = false.
  • y1=0y2=1y_1 = 0 \neq y_2 = 1, (10)mod2=10(1 - 0) \bmod 2 = 1 \neq 0. rows_ok = false.
  • Ответ: No

Крыша 3

w=2,h=2,a=1,b=1w=2, h=2, a=1, b=1, листы (0,0)(0, 0) и (1,1)(1, 1).

  • (10)mod1=0(1 - 0) \bmod 1 = 0. cols_ok = true (плюс x1x2x_1 \neq x_2).
  • Ответ: Yes

Всё сходится.


Крайние случаи

1. Листы в одном столбце (x1=x2x_1 = x_2)

Тогда cols_ok сразу false (из-за x1x2x_1 \neq x_2 условия). Остаётся проверить rows_ok: y1y2y_1 \neq y_2 (всегда так, т.к. листы не пересекаются) и (y2y1)modb=0(y_2 - y_1) \bmod b = 0.

2. Листы в одной строке (y1=y2y_1 = y_2)

Симметрично: rows_ok false, проверяется только cols_ok.

3. Отрицательные координаты

Листы могут стоять в x=a+1x = -a + 1 и т.д. Модульная арифметика по отрицательным числам в Python работает корректно ((-3) % 2 == 1), в C++ нужно учесть, что % может вернуть отрицательное. Но в задаче x2x1x_2 \geq x_1 не гарантируется, поэтому x2x1x_2 - x_1 может быть отрицательным. Однако для проверки «делится на aa» важен только остаток, и в C++ (-6) % 2 == 0 корректно — никаких проблем с отрицательными не возникает для нулевой проверки.

Но если вдруг решаешь на языке, где % по-другому работает, — используй abs(x2 - x1) % a == 0, это всегда корректно.

4. Максимальные значения

w,h,a,b109w, h, a, b \leq 10^9, координаты до 10910^9 по модулю. Разность координат — до 21092 \cdot 10^9, в int не помещается. Обязательно long long в C++.

5. n=104n = 10^4 крыш

10410^4 операций O(1)O(1) — мгновенно. Никаких оптимизаций ввода-вывода сверх обычных не требуется.


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

  1. Пропустить проверку x1x2x_1 \neq x_2 / y1y2y_1 \neq y_2. Если не добавить эту проверку, при x1=x2x_1 = x_2 получишь cols_ok = true (так как 0moda=00 \bmod a = 0), хотя по замощению «по строкам» два листа в одной точке невозможны. В таком случае правильно замостить только по столбцам — и rows_ok должна контролировать этот путь отдельно.

  2. Попытаться использовать w,hw, h. Возникает ощущение, что размер крыши влияет. На самом деле — нет: задача сведена к бесконечной сетке, а крыша просто вырезает из неё нужный кусок. Ответ зависит только от того, совместимы ли два данных листа с какой-то сеткой шага (a,b)(a, b).

  3. int в C++. Разность координат — до 21092 \cdot 10^9. В 32-битном int переполнение.

  4. Неверная реализация % для отрицательных. В Python % всегда неотрицателен, в C++ может быть отрицательным, но для проверки «равно 0» это не имеет значения. Проблема возникла бы только если сравнивать остаток с конкретным положительным числом — нам не нужно.

  5. Попытка перебрать все возможные листы. Размер крыши до 10910^9 — перебор невозможен. Решение концептуальное, не симуляционное.


Сложность

  • Время: O(1)O(1) на крышу, O(n)O(n) всего.
  • Память: O(1)O(1).

Что ещё полезно потренировать

Похожие задачи — на конструктивные алгоритмы и регулярные замощения:

  • Codeforces 1466C «Canine poetry» — конструктивное преобразование строки, ключ — увидеть инвариант.
  • Codeforces 1399D «Binary String To Subsequences» — распределение 0/1 на подпоследовательности, жадное построение.
  • Codeforces 1593B «Make it Divisible by 25» — математическая жадность + перебор случаев.
  • acmp.ru задача 266 «Парикмахерская» — жадное распределение по слотам.

Общий мотив: увидеть, что «ответ существует тогда и только тогда, когда выполнено простое арифметическое условие». Чаще всего — делимость, остатки, порядок.


Итого

  • Идея: любое корректное замощение идёт «по строкам» или «по столбцам» — листы образуют регулярную сетку.
  • Условие: (x1x2(x2x1)moda=0)(x_1 \neq x_2 \land (x_2 - x_1) \bmod a = 0) \lor (y1y2(y2y1)modb=0)(y_1 \neq y_2 \land (y_2 - y_1) \bmod b = 0).
  • Сложность: O(n)O(n).
  • Ловушки: забыть проверку x1x2x_1 \neq x_2 / y1y2y_1 \neq y_2; int вместо long long в C++; попытаться использовать w,hw, h (они не нужны).

Попробуй разобрать похожие задачи

В CodePal AI-партнёр подсказывает идею, а не ответ. Разбор в диалоге, код проверяется в браузере.