Google Kick Start: неправильное решение для сбора металла

#python #algorithm #optimization

#python #алгоритм #оптимизация

Вопрос:

Я пытаюсь попрактиковаться в Google Kick Start и решить задачу сбора металла:

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

Я прочитал анализ и понял его, но я не понимаю, почему мое решение не работает. Я всегда получаю «WA» — неправильный ответ при его отправке.

Основная идея заключается в том, что я сначала сортирую интервалы, затем группирую их в блоки, где между всеми интервалами в блоке меньше K временных шагов. Затем внутри блока я непрерывно развертываю роботов каждые K шагов, т.Е. Требую math.ceil(chunk_len / K) роботов для каждого блока. Мне не нужны роботы между блоками.

Это имеет смысл концептуально, верно? Он также проходит мои тестовые примеры. Итак, я предполагаю, что у меня есть какая-то ошибка в отношении индексов списка или граничных случаев, но я не могу понять, что именно.

 import math


class Interval:
    def __init__(self, start, end):
        self.start = start
        self.end = end


class Solution:
    def __init__(self, max_time, intervals):
        self.max_time = max_time
        self.intervals = intervals

    def harvest_v1(self):
        """start harvesting and return min number of robots"""
        num_robots = 0

        # sort intervals with increasing start time --> O(N log(N))
        sorted_intervals = sorted(self.intervals, key=lambda interval: interval.start)

        # walk through intervals, starting at  --> O(N)
        chunk_start = sorted_intervals[0].start
        for i in range(1, len(sorted_intervals)):
            # calc duration to next interval and stop chunk if there's K or more time until the next interval
            time_to_next = sorted_intervals[i].start - sorted_intervals[i-1].end
            if time_to_next >= self.max_time:
                # then stop chunk and compute num robots needed for chunk
                chunk_len = sorted_intervals[i-1].end - chunk_start
                num_robots  = math.ceil(chunk_len / self.max_time)
                # start a new chunk
                chunk_start = sorted_intervals[i].start

        # then still include last interval
        chunk_len = sorted_intervals[-1].end - chunk_start
        num_robots  = math.ceil(chunk_len / self.max_time)

        return num_robots


# read input and generate output
num_tests = int(input())
for test in range(1, num_tests   1):
    num_intervals, max_time = [int(k) for k in input().split()]
    intervals = []
    for interval in range(num_intervals):
        start, end = [int(k) for k in input().split()]
        intervals.append(Interval(start, end))

    sol = Solution(max_time, intervals)
    print("Case #{}: {}".format(test, sol.harvest_v1()))
  

Любые советы будут оценены!

Комментарии:

1. я думаю, что ваши пропущенные случаи, когда interval_size> максимальное время

Ответ №1:

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

     max_time = 6
    intervals = [Interval(1,3), Interval(4,7), Interval(11,16), Interval(17,22)]
  

Правильный вывод: 3

Результат вашей программы: 4

Объяснение

Ваш код помечает chunk start = start of the first interval и перематывает вперед, пока не найдет интервал с временным разрывом от предыдущего интервала >= max_time . Он вычисляет robot deployments требуемый до этого интервал, используя chunk start и end time текущего интервала в цикле. Поскольку ваш код обрабатывает только time_to_next >= self.max_time условие, его противоположное условие становится проблемой.

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


Если вы хотите попробовать другой подход, тогда взгляните на этот код:

 def harvest_v1(self):
    sorted_intervals = sorted(self.intervals, key=lambda interval: interval.start)
    robots_deployed = 0         # Keeps track of total robots deployed
    next_calibration_time = 0   # Keeps track of time for next robot calibration

    for interval in sorted_intervals:
        start_i = interval.start
        end_i = interval.end
        interval_duration = end_i - start_i

        if next_calibration_time <= start_i:    # If no robot has been deployed yet 
                                                # or previous robot has been sent for calibration, then
            
            # calculate the number of deployments required for current interval
            deployments_required = math.ceil(interval_duration / self.max_time)
            robots_deployed  = deployments_required

            # calculate and track the time at which lastly deployed robot will be sent for calibration
            next_calibration_time = start_i   (deployments_required * self.max_time)

        elif next_calibration_time < end_i:     # If some robot is still working then

            # calculate the time remaining in current interval after currently working robot becomes unavailable
            remaining_duration = end_i - next_calibration_time

            # calculate the number of deployments required
            deployments_required = math.ceil(remaining_duration / self.max_time)
            robots_deployed  = deployments_required

            # calculate and track the time at which lastly deployed robot will be sent for calibration
            next_calibration_time = next_calibration_time   (deployments_required * self.max_time)

    return robots_deployed
  

Комментарии:

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

2. Однако в вашем примере мой код работает нормально и возвращает правильное решение (4). Еще более простой пример, который завершается неудачей: K = 6 и 2 интервала [1,7] и [8,14]. Оба интервала имеют длину ровно 6 шагов, поэтому одного робота на интервал (всего 2) будет достаточно. Но поскольку разрыв между интервалами равен 1 math.ceil(13/6)=3

3. А, понятно. В предыдущем примере правильным решением было 3, а не 4, и мой подход ошибочен. Ответ принят 🙂

4. Мои ответы на тестовые примеры были неправильными раньше, я исправил это сейчас. Но сейчас вам это не понадобится, ваш тестовый пример проще понять.

5. Спасибо, что приняли мой ответ 🙂 Я рад, что смог помочь.