Какой алгоритм? (Рюкзак, упаковка мусорного ведра !?)

Проблема заключается в следующем:

У вас есть n длин пути в км, которые нужно разделить на m дней так, чтобы максимальная сумма длин в день была минимальна. Например. Продолжительность поездки [1,5,2,6,8,3,2], разделенная на 3 дня, дает [1,5,2] [6] [8,3,2], поскольку максимальная сумма продолжительности дня равна самое низкое, чего мы можем достичь.

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


person Igle    schedule 23.08.2016    source источник
comment
Это проблема динамического программирования и может быть решена в O(n * m)   -  person uSeemSurprised    schedule 23.08.2016
comment
Это задание колледжа или вопрос, заданный в какой-либо другой анкете?   -  person Ali786    schedule 23.08.2016
comment
Ну, проблема не совсем четко определена... Например, лучшее решение, чем предложенное, это: [1,5,2,6,8,3,2], [], [], где минимальная продолжительность дня равна 0, что лучше, чем 6. В любом случае, в наивном решении вы можете просто использовать binpacking и используйте бинарный поиск по параметру громкости.   -  person Bakuriu    schedule 23.08.2016
comment
@Bakuriu Я думаю, он хочет, чтобы максимальное значение среди m было сведено к минимуму.   -  person uSeemSurprised    schedule 23.08.2016
comment
Извините, я забыл слово. Мне нужно минимизировать максимальную сумму длин в день.   -  person Igle    schedule 23.08.2016


Ответы (3)


Эту проблему можно решить с помощью бинарного поиска.

Предположим, что максимальная длина равна X , мы можем легко проверить, можем ли мы разделить поездки на m дней с максимальной длиной для каждого дня, не превышающей X, следующим жадным выражением:

boolean isValid(int X){
   int currentLength = 0;
   int numberOfDays = 1;
   for(int i = 0; i < n; i++)
      if (trip[i] > X)
         return false;
      if(currentLength + trip[i] <= X){
         currentLength += trip[i];  
      }else{
         currentLength = trip[i];
         numberOfDays++;
      }
   }
   return numberOfDays <= m;
}

Тогда мы можем легко найти минимум для X с помощью следующего бинарного поиска:

int start = 0;
int end = sum of all trips;
int result = end;
while(start <=end){
    int mid = (start + end)/2;
    if(isValid(mid)){
       result = mid;
       end = mid - 1;
    }else{
       start = mid + 1;
    }
}

Временная сложность составляет O(n log z), где z — это сумма всех поездок.

person Pham Trung    schedule 23.08.2016
comment
Вау, это действительно хорошее решение! - person uSeemSurprised; 23.08.2016
comment
Очень мило, не могли бы вы дать краткое объяснение, почему это жадно работает? - person A. Sarid; 23.08.2016

Это можно решить с помощью подхода динамического программирования, в котором состояние определяется как DP[i][j], где i относится к конечному индексу дня, а j ведет подсчет дней до сих пор. Вы можете перейти к следующему состоянию и взять максимальную сумму за день, соответствующую текущему ходу, а затем сравнить ее с общим минимумом.

Я написал решение для рекурсивного динамического программирования на С++, может быть немного сложно понять, как работают переходы состояний, вам может потребоваться изучить динамическое программирование с мемоизацией, чтобы понять это.

#include <iostream>
#define INF 1000000000
using namespace std;

int n, m, dist[100] = {1,5,2,6,8,3,2}, dp[1000][1000];

int solve(int index, int count){
    if(index == n){
        if(count == m) return 0;
        else return INF;
    }
    if(dp[index][count] != -1) return dp[index][count];
    int sum = 0, ans = INF;
    for(int i = index;i < n;i++){
        sum += dist[i];
        int val = max(sum, solve(i+1, count+1));
        ans = min(ans, val);
    }
    return dp[index][count] = ans;
}

int main() {
    // your code goes here
    n = 7, m = 3;
    for(int i = 0;i < 1000;i++) for(int j = 0;j < 1000;j++) dp[i][j] = -1;
    cout << solve(0, 0) << endl;
    return 0;
}

Ссылка на решение Ideone: https://ideone.com/glHsgF

person uSeemSurprised    schedule 23.08.2016

Это не Knapsack и не Bin Packing. Обычно это называется проблемой k-partition.
Как упоминалось в комментариях, это можно сделать с помощью динамического программирования.

DP[N,M] - minimum cost of partitioning the array = {a1, ... , aN} into M partitions.
          Where cost is defined as the maximum sum of each partition.
DP[1,m] = a1
DP[n,1] = Sum of all elements in the array {a1, ... , an}
DP[n,m] = min over k from 1 to n ( max(DP[n-k,m-1],sum of elements n-k to n))
person A. Sarid    schedule 23.08.2016