Covariance / TrainStationScheduling

Train station scheduler for "Smart build queue" project of JetBrains internship 2020.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

TrainStationScheduling

Постановка задачи

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

Для каждого поезда известно:

  • номер поезда;
  • время прибытия;
  • время, которое у вас займёт разгрузка;
  • сумма, которую вам заплатят за разгрузку этого поезда. Начав разгрузку поезда, вы должны ее закончить и не можете разгружать два поезда одновременно.

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

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

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

Первая строка содержит одно цело число n — количество поездов в расписании. Каждая из следующих n строк сожержит по одному целому и три вещественных числа, причем i-ая строка содержит числа ni, ti, ki и si, — номер поезда, время его прибытия, длительность разгрузки и сумма награды за разгрузку. Гарантируется, что длительность разгрузки поезда — положительное число, а сумма награды за разгрузку — неотрицательное число.

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

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

Примеры входных данных

Входные данные Выходные данные
3 10.0
1 1 3 10 1
2 0 3 9 1
3 0 2 5
Входные данные Выходные данные
3 5.0
1 0 2 2 2
2 1 4 4 1 3
3 2 3 3

На первом тестовом примере не срабатывает алгоритм, при котором мы разгружаем поезд всегда, когда можем, на втором — простой жадный алгоритм.

Запуск решения

Есть два пути для запуска решения:

    1. Установить Apache Maven (или открыть проект в IntelliJ IDEA как Maven проект)
    2. Выполнить команду mvn -q compile exec:java 2>/dev/null (STDERR можно и не перенаправлять, но при определённых условиях Maven может выдавать WARNING: An illegal reflective access operation has occurred).
    3. Также можно выполнить встроенные тесты при помощи команды mvn test.
  1. Запустить скомпилированный файл FinalSolution при помощи команды java -jar FinalSolution.

Описание решения

Совсем наивный алгоритм

Переберём все возможные подмножества множества поездов, для каждого проверим, валидно ли оно для разгрузки (то есть что временные интервалы разгрузки поездов множества не накладываются), найдём среди них максимум. Временная ассимптотика решения — O(n·log(n)·2n), что, очевидно, слишком много.

Наивный алгоритм (NaiveSolution)

Воспользуемся методом динамического программирования. Отсортируем поезда по возрастанию времени прибытия, для каждого из них заведём состояние динамики dp[i]. Значение состояния динамики для i-ого поезда обозначает максимальную прибыль, которую можно получить, если обязательно разгрузить этот поезд и, возможно, какие-то из тех, которые пришли раньше него. Будем пересчитывать состояния динамики по возрастанию. Очевидно, что ответом для состояния i будет сумма награды за разгрузку i-ого поезда и максимума из всех состояний динамики dp[j], таких, что j < i и при итом временные интервалы разгрузки i-ого и j-ого поездов не пересекаются.

Ответом на задачу будет являться максимум по всем значениям динамики. Основные моменты этого алгоритма — сортировка поездов по времени и квадратичный проход по динамике. Таким образом, ассимптотика решения — O(n2) по времени и O(n) по памяти.

Преимущества

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

Недостатки

  • Без распараллеливания существенно дольше, чем нижеприведённый алгоритм.

Оптимальный алгоритм (OptimizedSolution)

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

Ответом на задачу будет значение динамики в последнем состоянии. Этот алгоритм работает за время сортировки массива событий и один линейный проход. Так как сортировка из стандартного класса Java.util.Collections работает за O(n·log(n)), то и весь алгоритм работает с такой же временной ассимптотикой и ассимптотикой O(n) по памяти.

Преимущества

  • Работает очень быстро, так как TimSort, используемая в Java очень эффективна.
  • Довольно очевидно, что решать эту задачу быстрее, чем за линейное время, невозможно (как минимум ввод входных данных занимает линейное время). Таким образом, бутылочным горлышком этого алгоритма является сортировка. Если бы время прибытия и длительность разгрузки были бы целочисленными переменными, можно было бы воспользоваться более быстрой сортировкой, например, countSort или radixSort.

Недостатки

  • Требует в несколько раз больше дополнительной памяти, чем наивный алгоритм, приведённый выше (массив событий имеет длину 2·n, как и массив динамики).
  • Единственное, что потенциально можно распараллелить в этом алгоритме — это сортировка, но, как известно, в большинстве случаев распараллеливание сортировки вещественных чисел требует больше времени из-за синхронизации потоков. Таким образом, можно признать, что этот алгоритм практически абсолютно не распараллеливается.

Сравнение алгоритмов

Наглядное сравнение исполнения этих алгоритмов приведено здесь.

About

Train station scheduler for "Smart build queue" project of JetBrains internship 2020.


Languages

Language:Java 91.4%Language:Python 4.8%Language:Shell 3.8%