В статье Пример как писать тесты в Yandex.Contest были даны рекомендации для успешного прохождения тестов компании Яндекс. Один из тестов - поиск наибольшего числа заказов для заданной площади прямоугольника, мы решали простым перебором всех заказов, что увеличивает сложность поиска решения в геометрической прогрессии. Но есть более изящное решение. Однако такие решения приходят не сразу - задача прорабатывается на подкорке некоторое время (несколько дней), а потом вдруг, когда едешь в метро или гуляешь с собакой, бац и решение готово почти мгновенно.

Эврика №1

Итак в чем тут "эврика"? Все заказы имеют координаты x и y. Тогда запишем их в 3-х мерный массив int[][][], где первый индекс будет x, второй - y, а значение - список номеров заказов в этой точке карты (одномерный массив int[]). Да, еще чтобы не создавать пустое место, определим минимальные значения координат у заказов и вычтем их из x и y - то есть сделаем "отстройку от пустоты".

Таким образом, перебирая точки внутри прямоугольника (алгоритм поиска его сторон дан в решении задачи в вышеуказанной статье) мы обращаемся напрямую по координатам в этом массиве к списку номеров заказов и если там пусто, то пропускаем точку, а иначе берем длину списка номеров заказов и суммируем с текущей суммой заказов внутри текущего прямоугольника. То есть "внутри" это значит по двум циклам (по x и y) от текущей точки положения прямоугольника и до его размера в этом цикле (x + dx и y + dy).

Эврика №2

Но это не все. Теперь мы можем начать вычиcлять нужные значения внутри прямоугольника не перебирая все точки внутри, а всего лишь вычитая все точки одного ребра и прибавляя все точки противоположного (если первый прямоугольник уже просчитан). Например, если мы двигаем прямоугольник скажем вверх по оси Y, тогда мы делаем перебор всех точек на нижнем его ребре (горизонтальном), и вычитаем их из общей суммы заказов внутри предыдущего прямоугольника, и берем ребро нового прямоугольника - на уровне y+dy+1 (противоположное горизонтальное) и все точки с него прибавляем к результату. Где dy - размер прямоугольника по оси Y, а y - текущий уровень просчета. Таким образом, мы значительно уменьшаем число подсчетов. Понятно что если мы двигаем прямоугольник по оси X, скажем слева направо, то так же обрабатываем его вертикальные стороны.

Эврика №3

Ну это не эврика конечно, но все же. Мы можем начать использовать промежуточные результаты, полученные при первом проходе (снизу вверх). Тогда дальше нам нет надобности просчитывать все положения каждый раз, мы опять будем использовать подход с вычитанием и сложением точек противоположных ребер, но уже двигаясь горизонтально - например слева направо. Это не сильно улучшит алгоритм - но можно поиграться еще с длинами сторон и выбирать направление первого прохода в зависимости от того какая из сторон прямоугольника меньше - вертикальная или горизонтальная.

Выводы

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

Комментарии (2)


  1. xtotdam
    05.11.2022 13:19
    +1

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


    1. Ermolaev-Dmitry Автор
      07.11.2022 11:05

      так я же говорю - это тесты от Яндекс. А там задачи в том числе на сообразительность. И поэтому задание ровно так и описано: "Задан список заказов с координатами (целые положительные числа) Задана площадь прямоугольника. Найти наибольшее число заказов, которые вмещаются в данный прямоугольник и вывести, это число и их список".
      Я кстати по началу подумал что они ошиблись и задан "квадрат". но потом понял свою ошибку. Текст задания полностью исчерпывающий, просто включите мозги.
      Подсказка - размеры прямоугольника меньше чем "простыня" на которой располагаются точки заказов. Эта подсказка лично моя - она вытекает из условия задачи.