【问题标题】:Maximizing count of distinct numbers that produce a given sum 'k'最大化产生给定总和“k”的不同数字的计数
【发布时间】:2016-09-03 13:30:40
【问题描述】:

我需要帮助解决这个动态规划问题。

给定一个正整数k,求和为k 的不同正整数的最大数目。例如,6 = 1 + 2 + 3,所以答案是 3,而 5 + 1 或 4 + 2 是 2。

我首先想到的是我必须找到一个子问题。所以要找到k 的最大和,我们需要找到小于k 的值的最大和。所以我们必须遍历值1 -> k 并找到这些值的最大总和。

让我困惑的是如何制作公式。我们可以将M(j) 定义为与j 相加的不同值的最大数量,但我该如何编写它的公式呢?

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

【问题讨论】:

  • 我不认为动态编程是必要的。只需找到最大的整数 n 使得 n(n+1)/2
  • 对此有一个封闭形式的解决方案,它比任何其他已发布的解决方案都更有效。我把它发布了一段时间,但人们不理解它并否决了它,所以我删除了它。您将不得不满足于效率较低的解决方案。
  • 所有你需要做的就是找到小于目标的最大三角数。该三角形数的顺序就是答案。
  • 如果不同的正整数是从输入数组中获取的,并且不是连续的正整数,那么这将是一个动态规划问题。所以我不得不怀疑你是否搞砸了问题描述。
  • 提示:在纸上解决数字 1 到 20 的问题。你能描述一下模式吗?

标签: c algorithm dynamic-programming


【解决方案1】:

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

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)

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

确实,如果我们恰好使用 n 个不同的正整数,那么具有这样和的最小数是 1+2+...+n = n(n+1)/2。通过解二次方,我们有 M(k) 大约是 sqrt(2k)。

因此算法是取数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;
    }
}

【讨论】:

    【解决方案2】:

    其他答案正确推断问题本质上是这个总结:

    然而这实际上可以简化为

    在代码中看起来像: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):https://math.stackexchange.com/q/123128

    【讨论】:

    • 如果你有一个恒定的时间解决方案,为什么要动态编程......非常好。
    • 从数学 SE 中找到完美答案的工作真棒!此外,“处理浮点数的缺点”根本不是缺点。可以通过使用分数和底平方根将其表示为整数算法。
    【解决方案3】:

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

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

    那么我们可以将k表示为i不同正整数之和:

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

    请注意,最后一项大于或等于i(因此与其他整数不同),因为k &gt;= T[i]。

    此外,不可能将k 表示为i+1 不同正整数之和,因为i+1 不同正整数之和的最小数字是T[i+1] &gt; k,因为我们选择了i .

    所以你的问题相当于找到最大的i 使得T[i] &lt;= 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)
    

    【讨论】:

    • 你知道这是一道 C 题,而不是 Python 题,对吧?
    【解决方案4】:

    我想你只需检查1 + ... + n &gt; k。如果是,打印n-1。

    因为如果你找到最小的n 为1 + ... + n &gt; k,那么1 + ... + (n-1) &lt;= 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.
    }
    

    【讨论】:

      【解决方案5】:

      这个问题可能不用动态编程就可以解决,但是我试着用动态编程的方式来看待它。

      提示:当你想解决一个动态规划问题时,你应该看到情况是“重复的”。在这里,因为从数字 k 的角度来看,例如,我先减 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)

      【讨论】:

        【解决方案6】:

        虽然尚不完全清楚您如何获得最大的离散数字系列可能存在哪些限制,但如果可以的话,传递一个简单的数组来保存离散数字,并在您的功能可以简化流程。例如,将数组 a long 与您当前的 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
        

        如果我在某处遗漏了对离散值选择的约束,我深表歉意,但以这种方式接近,您可以保证获得与您的总和相等的最大数量的离散值。如果您有任何问题,请告诉我。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-03-05
          • 2020-11-11
          • 2019-12-25
          • 1970-01-01
          • 1970-01-01
          • 2017-04-22
          相关资源
          最近更新 更多