2011-12-21 3 views
4

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

Вам предоставляется целочисленный массив с размером n < = 1000 и k < = n, который представляет собой число смежных подмассивов, которые вам нужно разделить на массив. Вы должны вывести минимум m, где m = max {s [1], ..., s [k]}, а s [i] - сумма i-го подмассива. Все целые числа в массиве составляют от 1 до 100. Пример:

Input:       Output: 
5 3 >> n = 5 k = 3    3 
2 1 1 2 3 

Разбиение массива на 2 + 1 | 1 + 2 | 3 минимизирует m.

Идея моей грубой силы заключалась в том, чтобы сделать первый субарейный конец в позиции i (для всех возможных i), а затем попытаться разбить остальную часть массива в k-1 подмассивах наилучшим образом. Однако это экспоненциальное решение и никогда не будет работать.

Итак, я ищу хорошие идеи для его решения. Если у вас есть один, скажите мне.

Благодарим за помощь.

+0

Back-трекинг получит вы там. – Noldorin

+0

Является ли это изменением проблемы с рюкзаком? http://en.wikipedia.org/wiki/Knapsack_problem – BlueMonkMN

+0

@BlueMonkMN Я думаю, что эта проблема проще. См. Мой ответ, где я не использую динамическое программирование. Тем не менее, я не совсем уверен, что это действительно или быстрее. – toto2

ответ

5

Вы можете использовать динамическое программирование для решения этой проблемы, но на самом деле вы можете решить с жадным и двоичным поиском ответа. Сложность этого алгоритма - O(n log d), где выходным ответом является d. (Верхняя граница была бы суммой всех элементов в массиве.) (Или O(n d) в размере выходных бит)

Идея состоит в бинарном поиске по вашему m, а затем с жадностью продвигается вперед на массив, добавив текущий элемент в раздел, если добавление текущего элемента не подталкивает его по текущему m - в этом случае вы начинаете новый раздел. Текущий m является успешным (и, таким образом, корректирует верхнюю границу), если используемые номера разделов меньше или равны вашему заданному входу k. В противном случае вы использовали слишком много разделов и повысили свою нижнюю границу на m.

Некоторые псевдокод:

// binary search 
binary_search (array, N, k) { 
    lower = max(array), upper = sum(array) 

    while lower < upper { 
     mid = (lower + upper)/2 

     // if the greedy is good 
     if partitions(array, mid) <= k 
      upper = mid 
     else 
      lower = mid 
    } 
} 

partitions(array, m) { 
    count = 0 
    running_sum = 0 

    for x in array { 
     if running_sum + x > m 
      running_sum = 0 
      count++ 
     running_sum += x 
    } 
    if running_sum > 0 
     count++ 
    return count 
} 

Это должно быть проще придумать концептуально. Также обратите внимание, что из-за монотонный характер функции разделов, вы можете пропустить бинарный поиск и сделать линейный поиск, если вы уверены, что выход d не слишком большой:

for i = 0 to infinity 
    if partitions(array, i) <= k 
     return i 
+0

Ничего себе, это такое простое решение для реализации и трудно найти (по крайней мере для меня). Спасибо за помощь. –

+0

Честно говоря, для некоторых проблем я считаю себя более уверенным в динамическом программировании только потому, что легче (для меня) доказать оптимальность, чем доказать, что жадность правильная. – Larry

+0

хороший Ларри, это здорово – FUD

3

Динамическое программирование. Сделать массив

int best[k+1][n+1]; 

best[i][j], где это лучшее, что вы можете достичь расщепления первые j элементы массива Int i подмассива. best[1][j] - это просто сумма первых элементов массива j. Имея ряд i, рассчитать ряд i+1 следующим образом:

for(j = i+1; j <= n; ++j){ 
    temp = min(best[i][i], arraysum[i+1 .. j]); 
    for(h = i+1; h < j; ++h){ 
     if (min(best[i][h], arraysum[h+1 .. j]) < temp){ 
      temp = min(best[i][h], arraysum[h+1 .. j]); 
     } 
    } 
    best[i+1][j] = temp; 
} 

best[m][n] будет содержать решение. Алгоритм O (n^2 * k), возможно, что-то лучше.

Редактировать: сочетание идей ChingPing, toto2, Coffee on Mars и rds (в ​​том порядке, в котором они отображаются, как я сейчас вижу эту страницу).

A = ceiling(sum/k). Это минимальная оценка минимума. Чтобы найти хорошую верхнюю границу для минимума, создайте хороший раздел любым из указанных способов, перемещая границы, пока вы не найдете какого-либо простого хода, которое все равно уменьшит максимальную дозу. Это дает вам верхнюю границу B, не намного большую, чем нижняя граница (если бы она была намного больше, вы бы нашли легкое улучшение, двигая границу, я думаю). Теперь переходим к алгоритму ChingPing с известной верхней границей, уменьшающей количество возможных ветвей. Эта последняя фаза O ((B-A) * n), нахождение B неизвестно, но я думаю, лучше, чем O (n^2).

+1

Я думаю, что это сработает: D просто предложение, так как существует ограничение на значение каждого элемента как 100 ... мы можем прекомпретировать значение arraysum [0 ... j] для j = 0 до n .., а затем arraysum [i ... j] == arraysum [0 ... j] -arraysum [0 ... i] .., которые сводят его к O (n * k) – FUD

+1

Да, я бы также сохранил суммарную сумму в массиве, поэтому 'arraysum [a .. b]' станет 'cum [b] - cum [a-1]', но это не делает его O (n * k), квадратичным в 'n' поведение возникает из-за того, что мы должны проверить возможные позиции jj' для последнего подмассива, чтобы найти «best [i + 1] [j]». Конечно, можно коротко сократить некоторые постоянные факторы. –

+0

err..ororry вы правы .. просто одна вещь .. вы думаете, что это должно быть лучше [i + 1] [j] = min (best [i + 1] [j], temp) – FUD

2

У меня есть Sucky ветвей и границ (пожалуйста, не downvote меня)

Сначала возьмите сумму массива и dvide по к, что дает вам лучший случай, связанный для вас ответить на то среднее А. Кроме того, мы будет поддерживать наилучшее решение, рассматриваемое до сих пор для любой ветви GO (глобальный оптимальный). Давайте рассмотрим, что мы поместили разделитель (логический) в качестве единицы раздела после некоторого элемента массива, и нам нужно поместить разделы k-1. Теперь мы будем размещать разделы с жадностью таким образом,

Пройдите по элементам массива, суммируя их до тех пор, пока вы не увидите, что на следующем месте мы будем превышать A, теперь сделаем две ветви, где вы поместите разделитель в этом положении, а также где вы ставите на следующую позицию, выполните это рекурсивно и установите GO = min (GO, ответьте на ветку). Если в какой-либо точке любой ветви мы имеем разбиение больше, чем GO, или no позиции меньше, то разделы, оставшиеся для привязки. В конце концов, вы должны идти, когда будете отвечать.

EDIT: По предложению Даниила, мы могли бы изменить размещение СТРАТЕГИИ делителя немного, чтобы поместить его, пока не дойдет до суммы элементов, как и остальных позиций слева, меньше делителей.

+0

Я думаю, что в целом это будет хорошо. Улучшение будет состоять в том, чтобы пересчитать возможный оптимум в каждой точке, где вы должны пересечь его. Начнем с 'A = потолка (sum/k)'. В первой точке, где 'running_sum [i]' будет превышать 'A', вычислите' B = потолок ((sum-running_sum [i-1])/(k-1)) '.Если 'B> = running_sum [i]', вам не нужно вступать в ветви и может обновлять 'A = running_sum [i]'. Точно так же для более поздних точек пересечения, если оставшееся среднее значение будет больше, чем то, что вы получаете путем пересечения, вам не нужно вступать. –

+1

Спасибо, ребята, за щедрые upvotes, но мое решение не так хорошо во всех случаях ... рассмотрите 1 1 1 9 как массив и 3 раздела, мое решение никогда не дойдет до ответа .. i would not mind unoing upvotes .. :) – FUD

+1

Хорошо, работайте в таких экстремальных случаях: 'A = max {max array, ceiling (sum/k)}', не выполняйте пока что у вас осталось меньше элементов, чем оставшихся подмассивов. Тогда я все еще думаю, что это очень хорошо. –

1

Это всего лишь эскиз идеи ... Я не уверен, что она работает, но это очень просто (и, вероятно, тоже быстро).

Начните с того, что разделители распределены равномерно (на самом деле не важно, как вы начинаете).

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

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

EDIT: см. Комментарий от @rds. Вам придется больше думать о подпрыгивающих решениях и о конечных условиях.

+0

круто, я думаю, это здорово! может быть сделано с помощью кучи! – FUD

+1

Неправильное использование. Пример счетчика: [1; 40; 50; 1; 2; 40] с k = 3. Начните с (1; 40) (50; 1) (2; 40). Максимум посередине. Слева - ниже. Переход к (1; 40; 90) (1) (2; 40). Возвращается к (1; 40) (50; 1) (2; 40). Окончание. То есть есть (1; 40) (50) (1; 2; 40). – rds

+0

@rds Спасибо. Вот почему у меня было «вероятно ** ** означает, что у вас есть решение». Я добавлю изменения. – toto2

0

Если ваш массив имеет случайные числа, вы можете надеяться, что раздел, в котором каждый подмассив имеет n/k, является хорошей отправной точкой.

Оттуда

  1. Оценить этот вариант решения, путем вычисления суммы
  2. магазин этот вариант решения. Например, с:
    • массив из индексов каждого подмассивов
    • соответствующий максимум суммы по подмассивов
  3. Уменьшить размер максимальной подрешетки: создать два новых кандидатов : один с суб-массивом, начинающимся с индекса + 1; один с суб-массивом, заканчивающимся на индекс-1
  4. Оцените новых кандидатов.
    • Если их максимум выше, отбросить
    • Если их максимум меньше, итерацию по 2, за исключением того, если этот кандидат уже оценили, в этом случае это решение.
0

Моя идея, которая, к сожалению, не работает:

  1. Разбить массив в N подмассивов
  2. Найдите два смежных подмассива, сумма которых меньше
  3. Объединить подмассива найдено на шаге 2, чтобы сформировать новый непрерывный подрамник
  4. Если общее количество подмассивов больше k, повторите операцию с шага 2, иначе закончите.
+0

К сожалению, это не всегда работает. Для примера это создаст '2 | 1 1 | 2 3' в качестве первого шага и заканчивая '2 1 1 | 2 | 3' или '2 | 1 1 2 | 3'. Затем вам нужно будет проверить, можете ли вы уменьшить максимум, перемещая границы. Это даст вам хорошую стартовую позицию для перемещения границ, возможно, и в большинстве случаев вы быстро найдете оптимальный вариант, но я не уверен, что могут быть случаи, когда вы застряли на локальном, но не глобальном минимуме , –

+0

Да, казалось бы, проблема с близкими двумя близкими значениями всегда приводит к локальному минимуму, потому что алгоритм объединит их, а не ближе. –

Смежные вопросы