Справка по алгоритму: как разделить массив на N сегментов с наименьшим возможным наибольшим сегментом (сбалансированное сегментирование)

Я столкнулся с этой проблемой на одном из российских форумов программирования, но не придумал элегантного решения.

:

У вас есть массив с N целых положительных чисел, вам нужно разделить его на M смежных сегментов, так что сумма наибольшего сегмента является наименьшим возможным значением. Под итогом сегмента я подразумеваю сумму всех его целых чисел. Другими словами, Мне нужен хорошо сбалансированный массив сегментация, где вы не хотите, чтобы один сегмент был слишком большим.

пример:

  • массив: [4, 7, 12, 5, 3, 16]

  • M = 3, Что означает, что мне нужно разделить мой массив на 3 подрешетки.

  • решение было бы: [4,7] [12, 5] [3, 16] таким образом, наибольший сегмент равен [3, 16] = 19, и ни один другой вариант сегментации не может создать наибольший сегмент с меньшим всего.

еще пример:

  • массив [3, 13, 5, 7, 18, 8, 20, 1]
  • M = 4

решение: [3, 13, 5] [7, 18] [8] [20, 1], в "жирный" сегмент [7, 18] = 25 (поправьте меня, если я ошибаюсь, Я сделал этот пример)

У меня такое чувство, что это какая-то классическая задача CS/math, вероятно, с именем какого-то известного человека, связанного с ней, как проблема Дийкстры. - Есть ли известное решение для этого? - Если нет, можете ли вы придумать какое-нибудь другое решение, кроме грубого принуждения, которое, насколько я понимаю сложность времени,экспоненциальный. (N^M, чтобы быть более конкретным).

заранее спасибо, stackoverflowers.

2 ответов


мне нравится подход ILoveCoding. Вот еще один способ, который занимает o (MN^2) Время, что будет быстрее, если N и M малы, но числа в массиве очень большие (в частности, если log(sum) >> MN, что возможно, но, по общему признанию, не звучит очень реалистично). Он использует динамическое программирование.

давайте рассмотрим разбиение только подмножества, состоящего из первых I

легко вычислить f (i, 1) -- это всего лишь сумма первых I элементов:

f(i, 1) = x[1] + ... + x[i]

чтобы вычислить f (i, j) для j >= 2, Обратите внимание, что элемент i должен быть окончательным элемент некоторого сегмента, который начинается с некоторой позиции 1 и в любом оптимальном решении для параметров (i, j) эти J-1 предшествующие сегменты сами должны быть оптимальными для параметров (k-1, j-1). Поэтому, если мы рассмотрим каждую возможную стартовую позицию k для этого конечного сегмента и возьмем лучшее, мы вычислим лучшее J-разбиение первых I элементов:

[EDIT 3/2/2015: нам нужно взять максимум нового сегмент и самый большой оставшийся сегмент, вместо того, чтобы добавлять их!]

f(i, j >= 2) = minimum of (max(f(k-1, j-1), x[k] + ... + x[i])) over all 1 <= k <= i

если мы попробуем K значений в порядке убывания, то мы можем легко построить сумму в постоянное время на K значение, поэтому вычисление одного значения f(i, j) занимает O(N) время. У нас есть MN этих значений для вычисления, поэтому общее время, необходимое O(MN^2).

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

f(i, j > i) = infinity

как только мы вычислили f (N, M), мы могли бы извлечь соответствующий раздел, проследив назад через матрицу DP обычным способом, но в этом случае, вероятно, проще просто построить раздел, используя жадный алгоритм ILoveCoding. В любом случае требуется время O(N).


  1. давайте сделаем двоичный поиск по ответу.

  2. для фиксированного ответа X легко проверить, возможно ли это или нет(мы можем просто использовать жадный алгоритм (всегда беря самый длинный возможный сегмент, чтобы его сумма была <= X) и сравните количество сегментов с M).

общая сложность времени O(N * log(sum of all elements)).

вот какой-то псевдокод

boolean isFeasible(int[] array, long candidate, int m) {
    // Here goes the greedy algorithm.
    // It finds the minimum number of segments we can get(minSegments).
    ...
    return minSegments <= m;
}

long getMinimumSum(int[] array, int m) {
    long low = 0; // too small
    long high = sum of elements of the array // definitely big enough
    while (high - low > 1) {
         long mid = low + (high - low) / 2;
         if (isFeasible(array, mid, m))
             high = mid;
         else
             low = mid;
    }
    return high;
}