k元素的最大总和不大于m

时间:2013-02-27 19:28:15

标签: algorithm

这个问题来自于编程竞赛,我无法在可接受的时间内解决它。

您将获得an整数的数组s。找到不超过给定整数k的确切m (s < m)元素(不一定是连续)的最大总和 0 < k <= n < 100 m < 3000 0 < a[i] < 100

约束:

{{1}}

信息:保证给定输入的解决方案。

现在,我想我最好的选择是DP方法,但无法提出正确的公式。

4 个答案:

答案 0 :(得分:2)

我会尝试两件事。它们都基于以下想法:

如果我们可以解决决定是否有k个元素将完全p相加的问题,那么我们可以在[1, m]中二进制搜索答案}。

<强> 1。优化的暴力

当当前总和超过p时,只需对数组进行排序并缩短搜索范围。这个想法是你通常只需要回溯很少,因为排序的数组应该有助于尽早消除不良解决方案。

说实话,我怀疑这会足够快。

<强> 2。随机算法

保留used大小为k的数组。随机分配元素。虽然它们的总和不是p,但是用另一个元素随机替换一个元素,并确保在恒定时间内更新它们的总和。

最多保持e次这样做(尝试使用其最佳结果值,最后复杂度为O(e log m),因此它可能会非常高),如果你不能在此期间得到总和p,假设它不可能。

或者,忘记二进制搜索。直接运行随机算法并返回它在e运行中找到的最大有效总和,或者直到您分配的运行时间结束。

我不确定DP如何有效地跟踪总和中使用的元素数量。我认为随机算法值得一试,因为它很容易实现。

答案 1 :(得分:2)

两种可接受的方法都不如人意。此外,这不是DP可以解决的问题类型。

以下是通过示例说明的正确方法:

想象a = {2,3,5,9,11,14,17,23}(因此n = 8),k = 3,s = 30

对数组进行排序a。

定义三个指向数组的指针,P1,P2和P3从1到n。 P1&lt; P2&lt; P3

将P3设置为a_max(此处为23),P1设置为1,P2设置为2.计算总和s(此处为23 + 2 + 3 = 28)。如果s> S,然后将P3减一,再试一次,直到找到解决方案。如果P3&lt; 3,那就没有解决方案了。将您的第一个解决方案存储为迄今为止最着名的解决方案(BKSSF)。

接下来,增加P2直到s> S.如果您找到更好的解决方案更新BKSSF。将P2减少一个。

接下来增加P1直到s> S.如果您找到更好的解决方案,请更新。

现在回到P2并将其减少一个。

然后增加P1直到s> S.等。

你可以看到这是一个递归算法,其中每增加或减少一个或多个相应的减少,增加。

此算法将比上述尝试快得多。

答案 2 :(得分:0)

表示l&lt; = k且r&lt; = s:

V [l] [r] =如果可以精确选择总计 r l 元素。

V[0][0] = true
for i in 1..n:
  V'[][] - initialize with false
  for l in 0..k-1:
    for r in 0..s:    
      if V[l][r] and s + a[i] <= s:
        V'[l + 1][r + a[i]] = true
  V |= V'

这给你所有可实现的总和O(k * n * s)。

答案 3 :(得分:0)

我认为Tyler Durden有正确的想法。但是你不必总结所有元素,你基本上可以贪婪地做,所以你可以减少循环。在C ++中:

#include <iostream>
#include <algorithm>
using namespace std;

#define FI(n) for(int i=0;i<(n);i++)

int m, n, k;
int a[] = { 12, 43, 1, 4, 3, 5, 13, 34, 24, 22, 31 },
    e[20];

inline int max(int i) { return n-k+i+1; }

void print(int e[], int ii, int sum)
{   cout << sum << '\t';
    FI(ii+1) cout << e[i]<<','; cout<<'\n';
}

bool desc(int a, int b) { return a>b; }

int solve()
{   sort(a, a+n, desc);
    cout <<"a="; FI(n) cout << a[i]<<','; cout<<"\nsum\tindexes\n";
    int i,sum;
    i = e[0] = sum = 0;
    print (e,i,a[0]);
    while(1)
    {   while (e[i]<max(i) && sum+a[e[i]]>=m) e[i]++;
        if (e[i]==max(i))
        {   if (!i) return -1;  // FAIL
            cout<<"*"; print (e,i,sum);
            sum -= a[e[--i]++];
        } else // sum+a[e[i]]<m
        {   sum += a[e[i]];
            print (e,i,sum);
            if (i+1==k) return sum;
            e[i+1] = e[i]+1;
            i++;
        }
    }
}

int main()
{   n = sizeof(a)/sizeof(int);
    k = 3;
    m = 39;
    cout << "n,k,m="<<n<<' '<<k<<' '<<m<<'\n';
    cout << solve();
}

对于m = 36,它给出了输出

n,k,m=11 3 36
a=43,34,31,24,22,13,12,5,4,3,1,
sum indexes
43  0,
34  1,
*34 1,10,
31  2,
35  2,8,
*35 2,8,11,
34  2,9,
35  2,9,10,
35

对于m = 37,它给出了

n,k,m=11 3 37
a=43,34,31,24,22,13,12,5,4,3,1,
sum indexes
43  0,
34  1,
*34 1,10,
31  2,
36  2,7,
*36 2,7,11,
35  2,8,
36  2,8,10,
36

(最后一次尝试:对于m = 39,它也给出正确答案,38) 输出:最后一个数字是总和,它上面的行有索引。带有星号的行在回溯之前,因此该行的最后一个索引太高。运行时应为O(k * n)。

对于难以理解的代码感到抱歉。我可以清理它并根据要求提供解释,但我现在有另一个项目;)。