具有最大相等和且不使用所有元素的子集和

Subset sum with maximum equal sums and without using all elements

给你一组整数,你的任务如下:将它们分成 2 个总和相等的子集,以使这些总和最大。您可以不使用所有给定的整数,这没关系。如果不可能,请以某种方式报告错误。

我的方法相当简单:在每一步中,我们选择一个项目,将其标记为已访问,更新当前总和并递归地选择另一个项目。最后,尝试跳过当前元素。

它适用于更简单的测试用例,但它失败了一个:

T = 1

N = 25

Elements: 5 27 24 12 12 2 15 25 32 21 37 29 20 9 24 35 26 8 31 5 25 21 28 3 5

可以运行如下:

1 25 5 27 24 12 12 2 15 25 32 21 37 29 20 9 24 35 26 8 31 5 25 21 28 3 5

我希望总和等于 239,但算法找不到这样的解。

我得到了以下代码:

#include <iostream>
#include <unordered_set>

using namespace std;

unordered_set<uint64_t> visited;

const int max_N = 50;
int data[max_N];

int p1[max_N];
int p2[max_N];

int out1[max_N];
int out2[max_N];

int n1 = 0;
int n2 = 0;

int o1 = 0;
int o2 = 0;

int N = 0;

void max_sum(int16_t &sum_out, int16_t sum1 = 0, int16_t sum2 = 0, int idx = 0) {
  if (idx < 0 || idx > N) return;

  if (sum1 == sum2 && sum1 > sum_out) {
    sum_out = sum1;
    o1 = n1;
    o2 = n2;
    for(int i = 0; i < n1; ++i) {
      out1[i] = p1[i];
    }
    for (int i = 0; i < n2; ++i) {
      out2[i] = p2[i];
    }
  }
  if (idx == N) return;

  uint64_t key = (static_cast<uint64_t>(sum1) << 48) | (static_cast<uint64_t>(sum2) << 32) | idx;

  if (visited.find(key) != visited.end()) return;

  visited.insert(key);

  p1[n1] = data[idx];
  ++n1;

  max_sum(sum_out, sum1 + data[idx], sum2, idx + 1);
  --n1;

  p2[n2] = data[idx];
  ++n2;

  max_sum(sum_out, sum1, sum2 + data[idx], idx + 1);
  --n2;

  max_sum(sum_out, sum1, sum2, idx + 1);
}

int main() {
  int T = 0;
  cin >> T;

  for (int t = 1; t <= T; ++t) {
    int16_t sum_out;

    cin >> N;
    for(int i = 0; i < N; ++i) {
      cin >> data[i];
    }

    n1 = 0;
    n2 = 0;

    o1 = 0;
    o2 = 0;

    max_sum(sum_out);

    int res = 0;
    int res2 = 0;

    for (int i = 0; i < o1; ++i) res += out1[i];
    for (int i = 0; i < o2; ++i) res2 += out2[i];

    if (res != res2) cerr << "ERROR: " << "res1 = " << res << "; res2 = " << res2 << '\n';

    cout << "#" << t << " " << res << '\n';

    visited.clear();
  }
}

我有以下问题:

  1. 有人可以帮我解决失败的测试吗?有什么明显的问题吗?
  2. 我怎样才能去掉 unordered_set 来标记已经访问过的总和?我更喜欢使用纯 C.
  3. 有没有更好的方法?也许使用动态规划?

1) 有人可以帮我解决失败的测试吗?有什么明显的问题吗?

我能看到的唯一问题是您没有将 sum_out 设置为 0。

当我尝试 运行 该程序时,它似乎对您的测试用例正常工作。

2) 我怎样才能去掉 unordered_set 来标记已经访问过的总和?我更喜欢使用纯 C.

查看问题 3 的答案

3) 有更好的方法吗?也许使用动态规划?

您目前正在记录您是否看到了 value for first subsetvalue for second subsetamount through array 的每个选项。

如果您改为跟踪值之间的差异,那么复杂性会大大降低。

特别是,您可以使用动态规划来存储数组 A[diff],对于差值的每个值,要么存储 -1(表示差值不可达到),要么存储 subset1 的最大值,当subset1 和 subset2 的差正好等于 diff.

然后您可以遍历输入中的条目并根据将每个元素分配给 subset1/subset2/ 或根本不分配来更新数组。 (请注意,您需要在计算此更新时制作数组的新副本。)

在这种形式中没有使用 unordered_set 因为你可以简单地使用一个直接的 C 数组。 subset1 和 subset2 之间也没有区别,所以你只能保持正差异。

示例Python代码

from collections import defaultdict

data=map(int,"5 27 24 12 12 2 15 25 32 21 37 29 20 9 24 35 26 8 31 5 25 21 28 3 5".split())

A=defaultdict(int) # Map from difference to best value of subset sum 1
A[0] = 0 # We start with a difference of 0

for a in data:
    A2 = defaultdict(int)
    def add(s1,s2):
        if s1>s2:
            s1,s2=s2,s1
        d = s2-s1
        if d in A2:
            A2[d] = max( A2[d], s1 )
        else:
            A2[d] = s1
    for diff,sum1 in A.items():
        sum2 = sum1 + diff
        add(sum1,sum2)
        add(sum1+a,sum2)
        add(sum1,sum2+a)
    A = A2
print A[0]

这将打印 239 作为答案。

为了简单起见,我没有考虑使用线性数组而不是字典进行优化。

一种非常不同的方法是使用约束或混合整数求解器。这是一个可能的公式。

x(i,g) = 1 if value v(i) belongs to group g
         0 otherwise

优化模型如下所示:

 max s
 s = sum(i, x(i,g)*v(i))  for all g
 sum(g, x(i,g)) <= 1      for all i

对于两组我们得到:

----     31 VARIABLE s.L                   =      239.000  

----     31 VARIABLE x.L  

             g1          g2

i1            1
i2                        1
i3                        1
i4                        1
i5            1
i6                        1
i7                        1
i8            1
i9                        1
i10           1
i11           1
i12           1
i13                       1
i14           1
i15           1
i16                       1
i17           1
i18                       1
i19                       1
i20           1
i21           1
i22           1
i23                       1
i25                       1

我们可以轻松地做更多的组。例如。 9 组:

----     31 VARIABLE s.L                   =       52.000  

----     31 VARIABLE x.L  

             g1          g2          g3          g4          g5          g6          g7          g8          g9

i2                        1
i3                                                                                                            1
i4                                                                        1
i5                                                1
i6                                                                                    1
i7            1
i8                        1
i9                                                                                                1
i10                                   1
i11           1
i12                                                                                   1
i13                                                                                               1
i14                                               1
i15                                                           1
i16                                                                       1
i17                                   1
i19                                               1
i20                                   1
i21                                                                                                           1
i22                                                                                   1
i23                                                           1
i24                                                                                                           1
i25                                                                       1

如果没有解,求解器将 select 将每个组中的元素归零,总和 s=0

另一种方法是考虑 [1,(2^N-2)] 之前的所有数字。 考虑每个位到每个元素位置的位置。迭代 [1,(2^N-2)] 中的所有数字,然后检查每个数字。 如果设置了位,您可以在 set1 中计算该数字,否则您可以将该数字放入 set2,然后检查两组的总和是否相等。在这里你会得到所有可能的集合,如果你只想要一个,一旦你发现刚刚打破。