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

时间:2018-02-24 12:46:40

标签: algorithm dynamic-programming mathematical-optimization discrete-mathematics

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

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

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

  

T = 1

     

N = 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

可以按如下方式运行:

  

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

我希望sum等于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 个答案:

答案 0 :(得分:2)

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作为答案。

为简单起见,我没有打扰使用线性数组而不是字典的优化。

相关问题