23

有一个数字列表。
该列表将被分成 2 个大小相等的列表,总和的差异最小。必须打印总和。

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

在某些情况下,以下代码算法是否有错误?

我如何优化和/或 pythonize 这个?

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/

4

14 回答 14

34

动态编程是您正在寻找的解决方案。

[4, 3, 10, 3, 2, 5] 的示例:

X 轴:组的可达总和。max = sum(所有数字)/ 2(四舍五入)
Y 轴:对组中的元素进行计数。最大值 = 计数 / 2(四舍五入)

      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)都是非负整数,则可以使用动态规划在伪多项式时间内解决背包问题。

于 2009-05-20T21:03:29.237 回答
6

新解决方案

这是具有启发式剔除的广度优先搜索。这棵树仅限于玩家/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 的人。

于 2009-05-20T22:07:21.013 回答
4

问:给定一个整数的多重集 S,有没有办法将 S 划分为两个子集S1 和 S2,使得S1 中的数字和等于 S2 中的数字之和?

A.设置分区问题

祝你好运。:)

于 2009-05-24T00:59:36.993 回答
2

好吧,您可以在多项式时间内找到百分比精度的解决方案,但要实际找到最佳(绝对最小差异)解决方案,问题是 NP 完全的。这意味着该问题没有多项式时间解。结果,即使数字列表相对较少,它的计算量也太大而无法解决。如果您真的需要解决方案,请查看一些用于此的近似算法。

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

于 2009-05-20T21:02:32.110 回答
1

请注意,它也是一种启发式方法,我将排序从函数中移出。

 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)
于 2009-05-20T21:24:42.223 回答
1

您的方法不起作用的测试用例是

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
于 2009-05-20T21:30:07.203 回答
1

它实际上是 PARTITION,KNAPSACK 的一个特例。

它是 NP Complete,具有伪多项式 dp 算法。伪多项式中的伪是指运行时间取决于权重范围的事实。

通常,您必须先确定是否存在精确解决方案,然后才能接受启发式解决方案。

于 2009-05-20T21:58:40.920 回答
1

他们显然在寻找动态编程背包解决方案。因此,在我的第一次努力(我认为是一个非常好的原始启发式)和我的第二次努力之后(一个非常狡猾的精确组合解决方案,它适用于短数据集,甚至适用于多达 100 个元素的集合,只要唯一值的数量是低),我终于屈服于同侪压力并写了他们想要的(不太难 - 处理重复的条目是最棘手的部分 - 我基于它的底层算法只有在所有输入都是唯一的情况下才有效 - 我很高兴long long足以容纳 50 位!)。

因此,对于我在测试前两次努力时汇总的所有测试数据和尴尬的边缘情况,它给出了相同的答案。至少对于我用组合求解器检查的那些,我知道它们是正确的。但是我仍然没有提交错误的答案!

我不是要求任何人在这里修复我的代码,但如果有人能找到下面代码生成错误答案的案例,我将非常感激。

谢谢,

格雷厄姆

PS 此代码总是在时间限制内执行,但远未优化。我一直保持简单,直到它通过测试,然后我有一些想法可以加快速度,可能提高 10 倍或更多。

#include <stdio.h>

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

静态int调试=真;

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

int main(int argc, char **argv) {
  诠释 p[101];
  字符 *s,行 [128];
  长长的掩码,c0[45001],c1[45001];
  int 技能,玩家,目标,i,j,测试,总计 = 0;

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

  s = fgets(行,127,标准输入);
  测试 = atoi(s);
  而(测试-> 0){

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

    s = fgets(行,127,标准输入);/* 空行 */
    s = fgets(行,127,标准输入);/* 玩家数量 */
    玩家 = atoi(s);
    for (i = 0; i < 玩家; i++) {s = fgets(line, 127, stdin); p[i] = atoi(s);}

    如果(玩家 == 1){
      printf("0 %d\n", p[0]);
    } 别的 {

    if (players&1) p[players++] = 0; // 通过添加一个 0 强度的单个玩家来修复奇数玩家
    //qsort(p, player, sizeof(int), simple);

    总计 = 0;for ( i = 0; i < 玩家; i++) 总计 += p[i];
    目标=总计/2;// 好的,如果总数是奇数并且结果向下取整 - n 组,n+1
    mask = 1LL << (((long long)players/2LL)-1LL);

    for (i = 0; i < 玩家; i++) {
      for (j = 0; j <= target; j++) {c1[j] = 0LL;} // memset 会更快
      技能 = p[i];
      //将此播放器添加到所有其他播放器和每个部分子集
      for (j = 0; j <= 目标技能; j++) {
        if (c0[j]) c1[j+skill] = c0[j]<<1; // 最高 = 最高 j+skill 用于后期优化
      }
      c0[技能] |= 1; // 所以我们不会给自己添加技能编号,除非它出现不止一次
      对于 (j = 0; j <= 目标; j++) {c0[j] |= c1[j];}
      if (c0[target]&mask) 中断;// 早日回归完美契合!
    }

    对于 (i = 目标; i > 0; i--) {
      if (debug || (c0[i] & mask)) {
        fprintf(stdout, "%d %d\n", i, total-i);
        如果(调试){
          if (c0[i] & mask) printf("******** ["); 别的
          printf("[");
          for (j = 0; j <= 玩家; j++) if (c0[i] & (1LL<<(long long)j)) printf(" %d", j+1);
          printf(" ]\n");
        } 否则中断;
      }
    }
    }
    if (测试) printf("\n");
  }
  返回0;
}
于 2009-11-15T20:03:26.143 回答
0
class Team(object):
    def __init__(self):
        self.members = []
        self.total = 0

    def add(self, m):
        self.members.append(m)
        self.total += m

    def __cmp__(self, other):
        return cmp(self.total, other.total)


def make_teams(ns):
    ns.sort(reverse = True)
    t1, t2 = Team(), Team()

    for n in ns:
        t = t1 if t1 < t2 else t2
        t.add(n)

    return t1, t2

if __name__ == "__main__":
    import sys
    t1, t2 = make_teams([int(s) for s in sys.argv[1:]])
    print t1.members, sum(t1.members)
    print t2.members, sum(t2.members)

>python two_piles.py 1 50 50 100
[50, 50] 100
[100, 1] 101
于 2009-05-20T21:03:19.410 回答
0

为了提高性能,您可以通过将 append() 和 sum() 替换为运行总计来节省计算。

于 2009-05-20T21:07:49.697 回答
0

您可以使用以下命令稍微收紧循环:

def make_teams(que):
    que.sort()
    t1, t2 = []
    while que:
        t1.append(que.pop())
        if sum(t1) > sum(t2):
            t2, t1 = t1, t2
    print min(sum(t1),sum(t2)), max(sum(t1),sum(t2))
于 2009-05-20T21:18:31.097 回答
0

由于列表必须与我相等,因此问题根本不是 NP。

我使用模式 t1<-que(1st, last), t2<-que(2nd, last-1) ... 拆分排序列表

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

编辑:我想这也是一个错误的方法。结果错误!

于 2009-05-20T21:33:06.790 回答
0

经过一番思考,对于不太大的问题,我认为最好的启发式方法如下:

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。

它几乎始终解决了上面提到的所有问题。

于 2009-05-20T22:01:34.597 回答
0

在较早的评论中,我假设设置的问题是易于处理的,因为他们在分配的时间内仔细选择了与各种算法兼容的测试数据。事实证明并非如此 - 相反,它是问题限制 - 不高于 450 的数字和不超过 50 个数字的最终集合是关键。这些与使用我在稍后的帖子中提出的动态编程解决方案解决问题兼容。其他算法(启发式算法,或组合模式生成器的详尽枚举)都无法工作,因为会有足够大或足够难的测试用例来破坏这些算法。老实说,这很烦人,因为其他解决方案更具挑战性,当然也更有趣。请注意,无需大量额外工作,

于 2009-11-14T00:17:37.430 回答