Алгоритм деления списка чисел на 2 списка равных сумм - PullRequest
23 голосов
/ 21 мая 2009

Есть список номеров.

Список должен быть разделен на 2 одинаковых по размеру списка с минимальной разницей в сумме. Суммы должны быть напечатаны.

#Example:
>>>que = [2,3,10,5,8,9,7,3,5,2]
>>>make_teams(que)
27 27

Есть ли ошибка в следующем алгоритме кода для некоторого случая?

Как мне оптимизировать и / или питонизировать это?

def make_teams(que):
    que.sort()
    if len(que)%2: que.insert(0,0)
    t1,t2 = [],[]
    while que:
    val = (que.pop(), que.pop())
    if sum(t1)>sum(t2):
        t2.append(val[0])
        t1.append(val[1])
    else:
        t1.append(val[0])
        t2.append(val[1])
    print min(sum(t1),sum(t2)), max(sum(t1),sum(t2)), "\n"

Вопрос от http://www.codechef.com/problems/TEAMSEL/

Ответы [ 14 ]

29 голосов
/ 21 мая 2009

Динамическое программирование - это решение, которое вы ищете.

Пример с [4, 3, 10, 3, 2, 5]:

X-Axis: Reachable sum of group.        max = sum(all numbers) / 2    (rounded up)
Y-Axis: Count elements in group.       max = count numbers / 2       (rounded up)

      1  2  3  4  5  6  7  8  9 10 11 12 13 14
 1  |  |  |  | 4|  |  |  |  |  |  |  |  |  |  |       //  4
 2  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |
 3  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |
      1  2  3  4  5  6  7  8  9 10 11 12 13 14
 1  |  |  | 3| 4|  |  |  |  |  |  |  |  |  |  |       //  3
 2  |  |  |  |  |  |  | 3|  |  |  |  |  |  |  |
 3  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |
      1  2  3  4  5  6  7  8  9 10 11 12 13 14
 1  |  |  | 3| 4|  |  |  |  |  |10|  |  |  |  |       // 10
 2  |  |  |  |  |  |  | 3|  |  |  |  |  |10|10|
 3  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |
      1  2  3  4  5  6  7  8  9 10 11 12 13 14
 1  |  |  | 3| 4|  |  |  |  |  |10|  |  |  |  |       //  3
 2  |  |  |  |  |  | 3| 3|  |  |  |  |  |10|10|
 3  |  |  |  |  |  |  |  |  |  | 3|  |  |  |  |
      1  2  3  4  5  6  7  8  9 10 11 12 13 14
 1  |  | 2| 3| 4|  |  |  |  |  |10|  |  |  |  |       //  2
 2  |  |  |  |  | 2| 3| 3|  |  |  |  | 2|10|10|
 3  |  |  |  |  |  |  |  | 2| 2| 3|  |  |  |  |
      1  2  3  4  5  6  7  8  9 10 11 12 13 14
 1  |  | 2| 3| 4| 5|  |  |  |  |10|  |  |  |  |       //  5
 2  |  |  |  |  | 2| 3| 3| 5| 5|  |  | 2|10|10|
 3  |  |  |  |  |  |  |  | 2| 2| 3| 5| 5|  |  |
                                       ^

12 - это наше счастливое число! Обратный путь для получения группы:

12 - 5 = 7        {5}
 7 - 3 = 4        {5, 3}
 4 - 4 = 0        {5, 3, 4}

Затем можно рассчитать другой набор: {4,3,10,3,2,5} - {5,3,4} = {10,3,2}

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

Кстати: это называется проблема с рюкзаком .

Если все веса (w1, ..., wn и W) неотрицательные целые числа, рюкзак проблема может быть решена в псевдополиномиальное время с использованием динамического программирования.

6 голосов
/ 21 мая 2009

Новое решение

Это поиск в ширину с эвристическим отбраковкой. Дерево ограничено глубиной игроков / 2. Лимит суммы игрока составляет итоговые баллы / 2. При пуле 100 игроков на решение проблемы ушло примерно 10 секунд.

def team(t):
    iterations = range(2, len(t)/2+1)

    totalscore = sum(t)
    halftotalscore = totalscore/2.0

    oldmoves = {}

    for p in t:
        people_left = t[:]
        people_left.remove(p)
        oldmoves[p] = people_left

    if iterations == []:
        solution = min(map(lambda i: (abs(float(i)-halftotalscore), i), oldmoves.keys()))
        return (solution[1], sum(oldmoves[solution[1]]), oldmoves[solution[1]])

    for n in iterations:
        newmoves = {}
        for total, roster in oldmoves.iteritems():
            for p in roster:
                people_left = roster[:]
                people_left.remove(p)
                newtotal = total+p
                if newtotal > halftotalscore: continue
                newmoves[newtotal] = people_left
        oldmoves = newmoves

    solution = min(map(lambda i: (abs(float(i)-halftotalscore), i), oldmoves.keys()))
    return (solution[1], sum(oldmoves[solution[1]]), oldmoves[solution[1]])

print team([90,200,100])
print team([2,3,10,5,8,9,7,3,5,2])
print team([1,1,1,1,1,1,1,1,1,9])
print team([87,100,28,67,68,41,67,1])
print team([1, 1, 50, 50, 50, 1000])

#output
#(200, 190, [90, 100])
#(27, 27, [3, 9, 7, 3, 5])
#(5, 13, [1, 1, 1, 1, 9])
#(229, 230, [28, 67, 68, 67])
#(150, 1002, [1, 1, 1000])

Также обратите внимание, что я попытался решить эту проблему, используя описание GS, но невозможно получить достаточно информации, просто сохранив промежуточные итоги. И если вы сохранили и количество элементов и итоги, то это будет то же самое, что и это решение, за исключением того, что вы хранили ненужные данные. Потому что вам нужно только сохранить n-1 и n итераций вплоть до numplayers / 2.

У меня был старый исчерпывающий, основанный на биномиальных коэффициентах (посмотрите в истории). Он отлично справился с задачами длиной 10, но потом я увидел, что в соревнованиях приняли участие люди длиной до 100.

2 голосов
/ 24 мая 2009

Q. Учитывая мультимножество S целых чисел , есть ли способ разбить S на два подмножества S1 и S2, чтобы сумма чисел в S1 равнялась сумме из чисел в S2?

A. Установить проблему с разделом .

Приближение удачи. :)

2 голосов
/ 21 мая 2009

Ну, вы можете найти решение с процентной точностью за полиномиальное время, но для того, чтобы найти оптимальное (абсолютное минимальное различие) решение, задача является NP-полной. Это означает, что нет решения проблемы за полиномиальное время. В результате, даже при относительно небольшом списке чисел, он слишком трудоемок для решения. Если вам действительно нужно решение, взгляните на некоторые алгоритмы аппроксимации для этого.

http://en.wikipedia.org/wiki/Subset_sum_problem

1 голос
/ 15 ноября 2009

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

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

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

Спасибо

Graham

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

#include <stdio.h>

#define TRUE (0==0)
#define FALSE (0!=0)

static int debug = TRUE;

//int simple(const void *a, const void *b) {
//  return *(int *)a - *(int *)b;
//}

int main(int argc, char **argv) {
  int p[101];
  char *s, line[128];
  long long mask, c0[45001], c1[45001];
  int skill, players, target, i, j, tests, total = 0;

  debug = (argc == 2 && argv[1][0] == '-' && argv[1][1] == 'd' && argv[1][2] == '\0');

  s = fgets(line, 127, stdin);
  tests = atoi(s);
  while (tests --> 0) {

    for (i = 0; i < 45001; i++) {c0[i] = 0LL;}

    s = fgets(line, 127, stdin); /* blank line */
    s = fgets(line, 127, stdin); /* no of players */
    players = atoi(s);
    for (i = 0; i < players; i++) {s = fgets(line, 127, stdin); p[i] = atoi(s);}

    if (players == 1) {
      printf("0 %d\n", p[0]);
    } else {

    if (players&1) p[players++] = 0; // odd player fixed by adding a single player of 0 strength
    //qsort(p, players, sizeof(int), simple);

    total = 0; for ( i = 0; i < players; i++) total += p[i];
    target = total/2; // ok if total was odd and result rounded down - teams of n, n+1
    mask = 1LL << (((long long)players/2LL)-1LL);

    for (i = 0; i < players; i++) {
      for (j = 0; j <= target; j++) {c1[j] = 0LL;} // memset would be faster
      skill = p[i];
      //add this player to every other player and every partial subset
      for (j = 0; j <= target-skill; j++) {
        if (c0[j]) c1[j+skill] = c0[j]<<1;  // highest = highest j+skill for later optimising
      }
      c0[skill] |= 1; // so we don't add a skill number to itself unless it occurs more than once
      for (j = 0; j <= target; j++) {c0[j] |= c1[j];}
      if (c0[target]&mask) break; // early return for perfect fit!
    }

    for (i = target; i > 0; i--) {
      if (debug || (c0[i] & mask)) {
        fprintf(stdout, "%d %d\n", i, total-i);
        if (debug) {
          if (c0[i] & mask) printf("******** ["); else
          printf("         [");
          for (j = 0; j <= players; j++) if (c0[i] & (1LL<<(long long)j)) printf(" %d", j+1);
          printf(" ]\n");
        } else break;
      }
    }
    }
    if (tests) printf("\n");
  }
  return 0;
}
1 голос
/ 21 мая 2009

Это на самом деле PARTITION, особый случай KNAPSACK.

Это NP Complete, с псевдополиномиальными алгоритмами dp. Псевдопсевдоним в псевдополиноме означает, что время выполнения зависит от диапазона весов.

Как правило, вам нужно сначала решить, существует ли точное решение, прежде чем допустить эвристическое решение.

1 голос
/ 21 мая 2009

Тестовый пример, где ваш метод не работает:

que = [1, 1, 50, 50, 50, 1000]

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

Вот код, который делает это

def make_teams(que):
    que.sort()
    que.reverse()
    if len(que)%2: que.insert(0,0)
    t1,t2 = [],[]
    while que:
        if abs(len(t1)-len(t2))>=len(que):
            [t1, t2][len(t1)>len(t2)].append(que.pop(0))
        else:
            [t1, t2][sum(t1)>sum(t2)].append(que.pop(0))
    print min(sum(t1),sum(t2)), max(sum(t1),sum(t2)), "\n"

if __name__=="__main__":
    que = [2,3,10,5,8,9,7,3,5,2]
    make_teams(que)
    que = [1, 1, 50, 50, 50, 1000]
    make_teams(que)

Это дает 27, 27 и 150, 1002, ответы на которые имеют смысл для меня.

Редактировать: В обзоре я считаю, что это на самом деле не работает, хотя в конце я не совсем уверен, почему. Я опубликую свой тестовый код здесь, хотя это может быть полезно. Тест только генерирует случайные последовательности, которые имеют равные суммы, складывает их и сравнивает (с печальными результатами).

Редактировать # 2: Исходя из примера, указанного Unknown, [87,100,28,67,68,41,67,1], ясно, почему мой метод не работает. В частности, для решения этого примера необходимо добавить два самых больших числа в одну и ту же последовательность, чтобы получить правильное решение.

def make_sequence():
    """return the sums and the sequence that's devided to make this sum"""
    while 1:
        seq_len = randint(5, 200)
        seq_max = [5, 10, 100, 1000, 1000000][randint(0,4)]
        seqs = [[], []]
        for i in range(seq_len):
            for j in (0, 1):
                seqs[j].append(randint(1, seq_max))
        diff = sum(seqs[0])-sum(seqs[1])
        if abs(diff)>=seq_max: 
            continue
        if diff<0:
            seqs[0][-1] += -diff
        else:
            seqs[1][-1] += diff
        return sum(seqs[0]), sum(seqs[1]), seqs[0], seqs[1]

if __name__=="__main__":

    for i in range(10):
        s0, s1, seq0, seq1 = make_sequence()
        t0, t1 = make_teams(seq0+seq1)
        print s0, s1, t0, t1
        if s0 != t0 or s1 != t1:
            print "FAILURE", s0, s1, t0, t1
1 голос
/ 21 мая 2009

Обратите внимание, что это также эвристика, и я убрал сортировку из функции.

 def g(data):
   sums = [0, 0]
   for pair in zip(data[::2], data[1::2]):
     item1, item2 = sorted(pair)
     sums = sorted([sums[0] + item2, sums[1] + item1])
   print sums

data = sorted([2,3,10,5,8,9,7,3,5,2])
g(data)
0 голосов
/ 14 ноября 2009

В более раннем комментарии я выдвинул гипотезу, что проблема в том виде, в каком она была установлена, поддается решению, потому что они тщательно выбрали тестовые данные для совместимости с различными алгоритмами в течение выделенного времени. Оказалось, что это не так - вместо этого существуют проблемы ограничения - числа не выше 450, а окончательный набор не больше 50 чисел является ключевым. Они совместимы с решением проблемы с использованием решения динамического программирования, которое я рассмотрю в следующем посте. Ни один из других алгоритмов (эвристика или исчерпывающее перечисление комбинаторным генератором шаблонов) не может работать, потому что будут тестовые случаи, достаточно большие или достаточно сложные, чтобы сломать эти алгоритмы. Честно говоря, это раздражает, потому что другие решения более сложные и, конечно, более веселые. Обратите внимание, что без большого количества дополнительной работы решение для динамического программирования просто говорит о том, возможно ли решение с N / 2 для любой заданной суммы, но не сообщает вам содержимое любого раздела.

0 голосов
/ 21 мая 2009

Подумав, для не слишком большой проблемы, я думаю, что лучшим видом эвристики будет что-то вроде:

import random
def f(data, nb_iter=20):
  diff = None
  sums = (None, None)
  for _ in xrange(nb_iter):
    random.shuffle(data)
    mid = len(data)/2
    sum1 = sum(data[:mid])
    sum2 = sum(data[mid:])
    if diff is None or abs(sum1 - sum2) < diff:
      sums = (sum1, sum2)
  print sums

Вы можете настроить nb_iter, если проблема больше.

Это решает все проблемы, упомянутые выше, в большинстве случаев.

...