23

番号のリストがあります。
このリストは、合計の差が最小限になるように、同じサイズの 2 つのリストに分割されます。合計を印刷する必要があります。

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

場合によっては、次のコード アルゴリズムにエラーがありますか?

これを最適化および/またはPython化するにはどうすればよいですか?

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 軸: グループ内の要素をカウントします。max = カウント数 / 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}

数字のあるすべてのフィールドは、1 つのバッグの可能なソリューションです。右下隅で最も遠いものを選択します。

ところで:それはナップザック問題と呼ばれています。

すべての重み (w1、...、wn および W) が非負の整数である場合、動的計画法を使用してナップザック問題を疑似多項式時間で解くことができます。

于 2009-05-20T21:03:29.237 に答える
6

新しいソリューション

これは、ヒューリスティック カリングを使用した幅優先検索です。ツリーはプレーヤー/2 の深さに制限されます。プレーヤーの合計制限は totalscores/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

Q.整数のマルチセット S が与えられた場合、S1 の数値の合計が S2 の数値の合計と等しくなるように、S を2 つのサブセット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

メソッドが機能しないテストケースは

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

問題は、物事をペアで分析していることです。この例では、すべての 50 代を同じグループに入れたいと考えています。ただし、ペア分析の側面を削除して、一度に 1 つのエントリだけを実行すると、これは解決されるはずです。

これを行うコードは次のとおりです

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]私の方法が機能しない理由は明らかです。具体的には、この例を解くには、有効な解を得るために 2 つの最大数を両方とも同じ数列に追加する必要があります。

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

それは実際には、KNAPSACK の特殊なケースである PARTITION です。

これは、疑似多項式 dp アルゴリズムを使用した NP 完全です。擬似多項式の擬似とは、実行時間が重みの範囲に依存するという事実を指します。

一般に、ヒューリスティックな解を認める前に、まず正確な解があるかどうかを判断する必要があります。

于 2009-05-20T21:58:40.920 に答える
1

彼らは明らかに動的プログラミング ナップザック ソリューションを探しています。したがって、最初の努力 (私が考えた非常に優れたオリジナルのヒューリスティック) と 2 回目の努力 (非常に卑劣な正確な組み合わせソリューションであり、短いデータ セットに対して機能し、一意の値の数が最大 100 の要素までのセットに対してさえ機能した後)低い)、私は最終的に仲間の圧力に屈し、彼らが望んでいたものを書きました(それほど難しいことではありません-重複したエントリの処理は最も難しい部分でした-私が基にした基礎となるアルゴリズムは、すべての入力が一意である場合にのみ機能します-私はそれを喜んでいますlong longは 50 ビットを保持するのに十分な大きさです!)。

したがって、最初の 2 つの作業をテストするときにまとめたすべてのテスト データとぎこちないエッジ ケースに対して、同じ答えが得られます。少なくとも、組み合わせソルバーでチェックしたものについては、それらが正しいことを知っています。しかし、私はまだいくつかの間違った答えで提出に失敗しています!

ここで私のコードを修正するように誰かに頼むつもりはありませんが、以下のコードが間違った答えを生成するケースを誰かが見つけてくれたら、とてもうれしいです。

ありがとう、

グラハム

PS このコードは常に制限時間内に実行されますが、最適化にはほど遠いものです。テストに合格するまではシンプルに保ちます。その後、おそらく10倍以上高速化するアイデアがあります。

#include <stdio.h>

#定義 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];
  長い長いマスク、c0[45001]、c1[45001];
  int スキル、プレイヤー、ターゲット、i、j、テスト、合計 = 0;

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

  s = fgets(行、127、標準入力);
  テスト = atoi(s);
  while (テスト --> 0) {

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

    s = fgets(行、127、標準入力); /* 空行 */
    s = fgets(行、127、標準入力); /* プレイヤー数 */
    プレイヤー=アトイ;
    for (i = 0; i < プレーヤー; i++) {s = fgets(行、127、stdin); p[i] = atoi(s);}

    もし (プレイヤー == 1) {
      printf("0 %d\n", p[0]);
    } そうしないと {

    if (プレイヤー&1) p[プレイヤー++] = 0; // 戦力 0 のプレイヤーを 1 人追加することで奇数プレイヤーを修正
    //qsort(p, プレーヤー, sizeof(int), simple);

    合計 = 0; for ( i = 0; i < プレーヤー; i++) 合計 += p[i];
    ターゲット = 合計/2; // 合計が奇数で、結果が切り捨てられた場合は OK - 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+スキル] = c0[j]<<1; // 最高 = 後で最適化するための最高の j+スキル
      }
      c0[スキル] |= 1; // そのため、スキル番号が複数回発生しない限り、それ自体にスキル番号を追加しません
      for (j = 0; j <= ターゲット; j++) {c0[j] |= c1[j];}
      if (c0[ターゲット]&マスク) ブレーク; // 完璧にフィットする早期復帰!
    }

    for (i = ターゲット; i > 0; i--) {
      if (debug || (c0[i] & mask)) {
        fprintf(stdout, "%d %d\n", i, total-i);
        if (デバッグ) {
          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 に答える
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 に答える
0

以前のコメントで、割り当てられた時間内にさまざまなアルゴリズムと互換性のあるテストデータを慎重に選択したため、設定された問題は扱いやすいと仮定しました。これは事実ではないことが判明しました-代わりにそれは問題の制約です-450以下の数と50以下の数の最終セットが鍵となります。これらは、後の投稿で取り上げた動的計画法ソリューションを使用して問題を解決することと互換性があります。他のアルゴリズム(ヒューリスティック、または組み合わせパターンジェネレーターによる徹底的な列挙)は、これらのアルゴリズムを破るのに十分な大きさまたはハードなテストケースが存在するため、機能しない可能性があります。これらの他のソリューションはより挑戦的で確かにもっと楽しいので、正直に言うとかなり面倒です。多くの余分な作業なしで、

于 2009-11-14T00:17:37.430 に答える
0

パフォーマンスのために、append() と sum() を実行中の合計に置き換えることで、計算を節約できます。

于 2009-05-20T21:07:49.697 に答える
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

リストは私が等しくなければならないので、問題は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

次を使用して、ループを少し締めることができます。

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

あまり大きな問題ではないので、少し考えた後、最良の種類のヒューリスティックは次のようなものになると思います。

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 に答える