最大化产生给定总和的不同数字的数量' k'

时间:2016-05-09 02:06:26

标签: c algorithm dynamic-programming

我需要有关此动态编程问题的帮助。

  

给定正整数k,找到总和为k的不同正整数的最大数量。例如,6 = 1 + 2 + 3,所以答案是3,而5 + 1或4 + 2则是2。

我想到的第一件事是我必须找到一个子问题。因此,要查找k的最大总和,我们需要找到小于k的值的最大总和。所以我们必须迭代值1 -> k并找到这些值的最大总和。

令我困惑的是如何制作一个公式。我们可以将M(j)定义为总和为j的不同值的最大数量,但我该如何为其编写公式?

到目前为止,我的逻辑是否正确,有人可以解释如何逐步完成这项工作吗?

6 个答案:

答案 0 :(得分:13)

不需要动态编程。让我们从一个例子开始:

50 = 50
50 = 1 + 49
50 = 1 + 2 + 47  (three numbers)
50 = 1 + 2 + 3 + 44  (four numbers)
50 = 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 14  (nine numbers)

我们可以去九个数字。如果我们使用10个数字,则总和将至少为1 + 2 + 3 + ... + 10 = 55,这大于50 - 因此这是不可能的。

实际上,如果我们使用完全 n 不同的正整数,那么这个和的最小数字是1 + 2 + ... + n = ñ名词 1)/ 2。通过求解二次方,我们得到 M k )近似为sqrt(2 k )。

因此算法是取数字 k ,减去1,2,3等,直到我们不能再减去,然后递减1.算法在C:

int M(int k) {
    int i;
    for (i = 1; ; i++) {
        if (k < i) return i - 1;
        else k -= i;
    }
}

答案 1 :(得分:8)

其他答案正确地推断出问题基本上就是这个总和:

然而,这实际上可以简化为

在代码中,这看起来像:floor(sqrt(2.0 * k + 1.0/4) - 1.0/2)

这个答案的缺点是需要你处理浮点数。

Brian M. Scott(https://math.stackexchange.com/users/12042/brian-m-scott),给定一个正整数,找到可形成其总和的最大不同正整数,URL(版本:2012-03-22):{{3} }

答案 2 :(得分:2)

可以表示为i个不同正整数之和的最小数字是1 + 2 + 3 + ... + i = i(i+1)/2,也称为i'三角数,T[i]。< / p>

允许i使T[i]是小于或等于k的最大三角数。

然后我们可以将k表示为i个不同正整数的总和:

1 + 2 + 3 + ... + (i-1) + (i + k - T[i])

请注意,最后一个术语大于或等于i(因此与其他整数不同),因为k >= T[i]

此外,无法将k表示为i+1个不同正整数的总和,因为i+1个不同正整数之和的最小数字是T[i+1] > k因为我们选择了i

因此,您的问题相当于找到最大的i T[i] <= k

这解决了这个问题:

 i = floor((-1 + sqrt(1 + 8k)) / 2)

[派生于此:https://math.stackexchange.com/questions/1417579/largest-triangular-number-less-than-a-given-natural-number]

您还可以编写一个简单的程序来迭代三角形数字,直到找到大于k的第一个:

def uniq_sum_count(k):
    i = 1
    while i * (i+1) <= k * 2:
        i += 1
    return i - 1

for k in xrange(20):
    print k, uniq_sum_count(k)

答案 3 :(得分:2)

我想你只是检查1 + ... + n > k。如果是,请打印n-1

因为如果您找到最小的n1 + ... + n > k,那么1 + ... + (n-1) <= k。所以将额外值(比如E)添加到(n-1),然后添加1 + ... + (n-1+E) = k

因此n-1是最大值。

  

注意:1 + ... + n = n(n + 1)/ 2

#include <stdio.h>

int main()
{
  int k, n;
  printf(">> ");
  scanf("%d", &k);
  for (n = 1; ; n++)
    if (n * (n + 1) / 2 > k)
      break;
  printf("the maximum: %d\n", n-1);
}

或者你可以M(j)

int M(int j)
{
  int n;
  for (n = 1; ; n++)
    if (n * (n + 1) / 2 > j)
      return n-1; // return the maximum.
}

答案 4 :(得分:0)

问题可能在没有动态编程的情况下解决,但我试图用动态编程方式来看待它。

提示:当你想要解决动态编程问题时,你应该看到情况是“重复”的。这里,从数量k的观点来看,例如,如果I先减1然后减3或先减3然后减1则无关紧要。我说“让我们按升序减去它”。 现在,重复了什么?好吧,我的想法是,我想从数字k开始,并从不同的元素中减去它,直到我得到零。所以,如果我达到的情况是我使用的剩余数字和最后一个不同的数字是相同的,情况是“重复”:

#include <stdio.h>

bool marked[][];
int memo[][];

int rec(int rem, int last_distinct){
    if(marked[rem][last_distinct] == true) return memo[rem][last_distinct]; //don't compute it again
    if(rem == 0) return 0; //success
    if(rem > 0 && last > rem - 1) return -100000000000; //failure (minus infinity)
    int ans = 0;
    for(i = last_distinct + 1; i <= rem; i++){
        int res = 1 + rec(rem - i, i); // I've just used one more distinct number
        if(res > ans) ans = res;
     }
    marked[rem][last_distinct] = true;
    memo[rem][last_distinct] = res;
    return res;
}

int main(){
   cout << rec(k, 0) << endl;
   return 0;
}

时间复杂度为O(k ^ 3)

答案 5 :(得分:0)

虽然目前还不完全清楚你如何达到最大离散数字系列的约束条件,但如果你能够做到这一点,可以通过一个简单的数组来保存离散数字,并在你的数字中保留一个运行总和功能可以简化过程。例如,将数组a与当前j一起传递给函数并返回构成数组中总和的元素数量可以通过以下方式完成:

int largest_discrete_sum (int *a, int j)
{
    int n, sum = 0;
    for (n = 1;; n++) {
        a[n-1] = n, sum += n;
        if (n * (n + 1) / 2 > j)
            break;
    }
    a[sum - j - 1] = 0;  /* zero the index holding excess */
    return n;
}

将它放在一个简短的测试程序中看起来像:

#include <stdio.h>

int largest_discrete_sum(int *a, int j);

int main (void) {

    int i, idx = 0, v = 50;
    int a[v];

    idx = largest_discrete_sum (a, v);
    printf ("\n largest_discrete_sum '%d'\n\n", v);
    for (i = 0; i < idx; i++)
        if (a[i])
            printf (!i ? "  %2d" : " +%2d", a[i]);
    printf (" = %d\n\n", v);
    return 0;
}

int largest_discrete_sum (int *a, int j)
{
    int n, sum = 0;
    for (n = 1;; n++) {
        a[n-1] = n, sum += n;
        if (n * (n + 1) / 2 > j)
            break;
    }
    a[sum - j - 1] = 0;  /* zero the index holding excess */
    return n;
}

示例使用/输出

$ ./bin/largest_discrete_sum

 largest_discrete_sum '50'

   1 + 2 + 3 + 4 + 6 + 7 + 8 + 9 +10 = 50

如果我错过了某处离散值选择的约束,我很抱歉,但是以这种方式接近你可以保证获得最大数量的离散值,它们将等于你的总和。如果您有任何问题,请告诉我。