У меня есть следующая проблема:
Данные объекты N (N <30) различных значений несколько из "k" константы т.е. k, 2k, 3k, 4k, 6k, 8k, 12k, 16k, 24k и 32k, мне нужен алгоритм, который распределит все объекты плеерам M (M <= 6) таким способом, которым итоговое значение объектов, которые получает каждый игрок, как, как раз когда возможный (другими словами, я хочу распределить все объекты всем плеерам самым справедливым возможным способом).
Править: Самым справедливым распределением я подразумеваю, что различие между значением объектов, которые получают любые два игрока, минимально. Другой подобный случай был бы: у Меня есть монеты N различных значений, и я должен разделить их одинаково между плеерами M; иногда они не делятся точно, и я должен найти следующий лучший случай распределения (где никакой плеер не сердит, потому что другой получил слишком много денег).
Мне не нужен (псевдо) код для решения этого (также, это не домашняя работа :)), но я буду ценить любые идеи или ссылки на алгоритмы, которые могли решить это.
Спасибо!
Задача является строго NP-полной. Это означает, что невозможно обеспечить правильное решение в разумные сроки. (См. Проблема с 3 разделами , спасибо, Пол).
Вместо этого вы захотите воспользоваться хорошим генератором приближенных решений. Часто они могут быть очень близки к оптимальному ответу за очень короткое время. Я могу порекомендовать метод Simulated Annealing , который вы также сможете использовать для множества других NP-полных задач.
Идея такова:
Это решение намного сильнее, чем многие предлагают «жадные» алгоритмы. Жадный алгоритм - это алгоритм, при котором вы постоянно добавляете самый крупный предмет «самому бедному» игроку.Пример тестового случая, когда жадный не работает: [10,9,8,7,7,5,5]
.
Я сделал для вас реализацию SA. Это строго следует за статьей вики, в образовательных целях. Если вы оптимизируете его, я бы сказал, что 100-кратное улучшение не будет нереалистичным.
from __future__ import division
import random, math
values = [10,9,8,7,7,5,5]
M = 3
kmax = 1000
emax = 0
def s0():
s = [[] for i in xrange(M)]
for v in values:
random.choice(s).append(v)
return s
def E(s):
avg = sum(values)/M
return sum(abs(avg-sum(p))**2 for p in s)
def neighbour(s):
snew = [p[:] for p in s]
while True:
p1, p2 = random.sample(xrange(M),2)
if s[p1]: break
item = random.randrange(len(s[p1]))
snew[p2].append(snew[p1].pop(item))
return snew
def P(e, enew, T):
if enew < e: return 1
return math.exp((e - enew) / T)
def temp(r):
return (1-r)*100
s = s0()
e = E(s)
sbest = s
ebest = e
k = 0
while k < kmax and e > emax:
snew = neighbour(s)
enew = E(snew)
if enew < ebest:
sbest = snew; ebest = enew
if P(e, enew, temp(k/kmax)) > random.random():
s = snew; e = enew
k += 1
print sbest
Обновление: Поигравшись с Branch'n'Bound, я теперь считаю, что этот метод лучше,поскольку он дает отличные результаты для случая N = 30, M = 6 в течение секунды. Однако я полагаю, что вы можете поиграться с методом имитации отжига точно так же.
как насчет этого:
упорядочить значения k. упорядочить игроков.
перебирайте значения k, передавая следующее значение следующему игроку. когда вы дойдете до конца игроков, развернитесь и продолжайте раздавать значения k игрокам в обратном направлении.
Жадное решение, предложенное несколькими людьми, кажется лучшим вариантом, я запускал его несколько раз с некоторыми случайными значениями, и, кажется, каждый раз все получается правильно.
Если это не оптимально, то, по крайней мере, очень близко, и оно выполняется за O (нм) или около того (я не могу сейчас потрудиться с математикой)
Реализация C #:
статический список
> Dist (значения int n, IList
) { var result = new List > (); для (int i = 1; i <= n; i ++) result.Add (новый список
()); var sortedValues = values.OrderByDescending (val => val); foreach (int val в sortedValues) { var low = result.OrderBy (a => a.Sum ()). First (); самый низкий.Добавить (val); } вернуть результат; }
РЕДАКТИРОВАТЬ:
Целью было использовать жадное решение с небольшим улучшением реализации, которое может быть прозрачным в C #:
static List<List<int>> Dist(int n, IList<int> values)
{
var result = new List<List<int>>();
for (int i = 1; i <= n; i++)
result.Add(new List<int>());
var sortedValues = values.OrderByDescending(val => val);//Assume the most efficient sorting algorithm - O(N log(N))
foreach (int val in sortedValues)
{
var lowest = result.OrderBy(a => a.Sum()).First();//This can be done in O(M * log(n)) [M - size of sortedValues, n - size of result]
lowest.Add(val);
}
return result;
}
Относительно этого этапа:
var lowest = result.OrderBy(a => a.Sum()).First();//This can be done in O(M * log(n)) [M - size of sortedValues, n - size of result]
Идея состоит в том, что список всегда отсортирован (в этот код делает OrderBy). В конце концов, эта сортировка не займет больше, чем O (log (n)) - потому что нам просто нужно ВСТАВИТЬ не более одного элемента в отсортированный список - что должно принимать то же самое, что и двоичный поиск. Поскольку нам нужно повторить эту фазу для sortedValues.Length раз, весь алгоритм выполняется за O (M * log (n)).
Таким образом, это можно перефразировать так:
Повторяйте шаги ниже, пока не закончите значения Значения
:
1. Добавьте наибольшую ценность к наименьшему игроку.
2. Проверьте, есть ли у этого игрока наименьшая сумма.
3. Если да, переходите к шагу 1.
4. Вставьте последнего полученного игрока в отсортированный список игроков
Шаг 4 - это шаг O (log (n)), поскольку список всегда отсортирован.
Несколько раз отдавайте доступный объект с наибольшим значением игроку, у которого наименьшее общее значение назначенных ему объектов.
30 ^ 6 не так уж и много (это меньше 1 миллиарда). Переберите все возможные варианты распределения и выберите тот, который является самым справедливым по любой мерке, которую вы определите.
Это прямая реализация ответа Джастина Пила:
M = 3
players = [[] for i in xrange(M)]
values = [10,4,3,1,1,1]
values.sort()
values.reverse()
for v in values:
lowest=sorted(players, key=lambda x: sum(x))[0]
lowest.append(v)
print players
print [sum(p) for p in players]
Я новичок в Python, но, кажется, все работает нормально. Этот пример выведет
[[10], [4, 1], [3, 1, 1]]
[10, 5, 5]