【问题标题】:Largest sum of k elements not larger than m不大于 m 的 k 个元素的最大和
【发布时间】:2013-02-27 19:28:14
【问题描述】:

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

给你一个数组a n 整数。找出不超过给定整数m (s < m) 的恰好k 元素(不一定连续)的最大和s

约束:

 0 < k <= n < 100
 m < 3000
 0 < a[i] < 100

信息:对于给定的输入,保证存在解决方案。

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

【问题讨论】:

  • 你说编程比赛。所以可能有额外的限制,例如在语言或运行时。如果可以忽略运行时约束,只需确定所有 k 元素子集,计算总和并取其最好的。
  • @UdoKlein 因为它在编程比赛中,有限制:) C++ 时间限制是 3 秒,Java 是 5 秒。内存限制不用担心(我认为是 256 MB)
  • @UdoKlein - 编程竞赛意味着需要一个最佳解决方案,或者至少比蛮力更好的解决方案 - 在任何具有给定约束的 PC 上可能需要数年才能完成。
  • @Debnis:所以如果已知约束,请将它们放入问题中。
  • 这是来自hackerrank.com的竞赛​​span>

标签: algorithm


【解决方案1】:

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

如果我们能解决判断是否有k元素恰好p相加的问题,那么我们可以在[1, m]中二分查找答案。

1.优化暴力破解

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

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

2。随机算法

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

最多保持e 次(实验其值以获得最佳结果,复杂度最终将是O(e log m),因此它可能会变得相当高),如果你无法求和p 在这段时间内,假设不可能。

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

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

【讨论】:

  • 面团,我完全忘记了我可以对数组进行排序,因为顺序无关紧要。 1. 解决方案已经成功了(一切都低于 0.3 秒),因为输入数组非常小。但是,是的,在更高阶的输入上,蛮力是完全不能接受的。谢谢!
【解决方案2】:

两种公认的方法都较差。另外,这不是DP能解决的问题类型。

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

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

对数组 a 排序。

在数组中定义三个指针,P1、P2 和 P3,从 1 到 n。 P1

将 P3 设置为 a_max(此处为 23),将 P1 设置为 1,将 P2 设置为 2。计算总和 s(此处为 23 + 2 + 3 = 28)。如果 s > S,则将 P3 减一并重试,直到找到解决方案。如果 P3

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

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

现在回到 P2 并减一。

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

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

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

【讨论】:

  • Tyler Durden 请提供一些代码!算法不清楚!
【解决方案3】:

对于 l

V[l][r] = true 如果有可能准确地选择总和为 rl 个元素。

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) 中所有可实现的总和。

【讨论】:

    【解决方案4】:

    我认为 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)。

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

    【讨论】:

      猜你喜欢
      • 2015-10-06
      • 2021-06-04
      • 2020-12-28
      • 2013-08-06
      • 2013-09-25
      • 1970-01-01
      • 1970-01-01
      • 2013-02-11
      • 1970-01-01
      相关资源
      最近更新 更多