【问题标题】:Algorithmic puzzle: Ball Stacking Problem算法难题:球堆积问题
【发布时间】:2019-12-20 20:58:03
【问题描述】:

我正在尝试解决这个问题:https://www.urionlinejudge.com.br/judge/en/problems/view/1312

XYZ 电视频道正在开发一个新的游戏节目,其中一位参赛者 必须做出一些选择才能获得奖品。游戏包括 一堆三角形的球,每个球都有一个整数值,如 下面的例子显示。

参赛者必须选择他要拿的球和他的 奖品是这些球的价值的总和。然而,参赛者 只有当他也直接接球时才能接任何给定的球 最重要的是。这可能需要使用相同的其他球 规则。请注意,参赛者可以选择不拿任何球,在 在这种情况下奖金为零。

电视节目导演担心 参赛者可以获得给定筹码的最高奖金。既然他是 你的老板,他不知道如何回答这个问题,他 将此任务分配给您。

输入

每个测试用例都用几行来描述。第一行 包含一个整数N,表示堆栈的行数(1 ≤ N ≤ 1000)。接下来 N 行的第 i th 行包含 i 个整数 Bij (−105 ≤ Bij ≤ 105 1 ≤ jiN);数字 Bij 是 堆栈的第 i th 行中的第 j th 个球(第一行是 最上面的一个,如果是最左边的,则在每行内第一个球)。

最后一个测试用例后跟一行包含一个零。

输出

Sample Input  | Sample Output
4             |   7 
3             |   0
-5 3          |   6
-8 2 -8       |
3 9 -2 7      |
2             |
-2            |
1 -10         |
3             |
1             |
-5 3          |
6 -4 1        |
0             |

我很想知道如何解决这个问题。

似乎可以使用 DP 方法解决,但我不能完全制定递归。两个相邻的球可能有重叠的孩子,这让事情变得有点困难。

【问题讨论】:

  • 输出是什么?它没有解释你实际试图计算的内容?这是最高分吗?
  • 到目前为止你尝试了什么?
  • 如果有人想知道为什么第一个输出是 7:从最后一行选择球 3 和 9,那么你还必须从第三行获得 -8 和 2,-5 和第 2 行 3 个,第 1 行 3 个。 3 + 9 - 8 + 2 - 5 + 3 + 3 = 7。
  • 这对于大型案例来说似乎非常繁重。当 N=1000 时,有 500500 个球可供选择,给出 2^500500 种可能的组合。尽管它们中的大多数都崩溃到相同的配置,但仍然会有太多的可能性进行测试(并且没有足够的内存来存储 DP)。通常,即使 N=100 似乎也很难解决。一些技巧和一些运气可以帮助消除部分可能性,但永远不足以使问题变得可行。
  • 我认为这可以被视为寻路问题,但您可能会从底部取出 3 个或更多球,它们之间有多个空格,这意味着排除了寻路。

标签: algorithm dynamic-programming


【解决方案1】:

这是 DP,但我们要横向而不是自上而下。让我们将球堆稍微向左倾斜,这样我们就可以将整个堆栈视为一系列列。

 3  3 -8  7
-5  2 -2
-8  9
 3

从这个角度看,游戏规则变成:如果我们想拿球,我们也需要把球拿在上面,然后把球直接放在左边。

现在,解决问题。我们将为每个球计算一个数量S[i, j] -- 这表示如果取位置 [i, j] 的球(第 i 列顶部的第 j 个球),我们可以达到的最佳总和,同时仅考虑前 i 列 .

我声称以下递归成立(具有一些合理的初始条件):

S[i, j] = MAX(S[i-1, j] + C[i, j], S[i, j+1])

其中C[i, j] 是第 i 列中前 j 个球的总和。

让我们稍微分解一下。我们要计算S[i, j]

  • 我们必须在 [i, j] 处接球。现在让我们假设这是我们从该列中取出的最底部的球。
  • 这要求将其上方此列中的所有球都拿走,总和(包括 [i, j] 本身)为C[i, j]
  • 它还需要在 [i-1, j] 处接球(当然,除非我们在最左边的列)。根据定义,我们知道拿下这个球的最佳金额是S[i-1, j]
  • 所以最好的总和是:S[i-1, j] + C[i, j],或者只是C[i, j] 最左边的列。
  • 但我们可以选择不同的方式并从该列中取出更多的球(如果我们有更多的球)。我们需要计算并从S[i-1, j] + C[i, j]S[i-1, j+1] + C[i, j+1] 等中取出最大值,一直到堆的底部。 稍微归纳一下,很容易看出这等于MAX(S[i-1, j] + C[i, j], S[i, j+1])

现在的实现应该很明显了。我们逐列处理堆栈,在每一列中从上到下计算部分和C[i, j],然后从下到上计算S[i, j]。 最后,只需将我们遇到的S[i, j] 的最大值(或0)作为答案。

这与球的数量成线性关系,所以 O(N^2)。

为了说明,这里是给定示例的 (C[i, j], S[i, j]) 对。

(  3, 3) ( 3,7) ( -8,-1)  (7,6)
( -2,-2) ( 5,7) (-10,-3) 
(-10,-7) (14,7)
( -7,-7)

【讨论】:

    【解决方案2】:

    (通过 Worakarn Isaratham 的answer 更好地理解更新。)

    通过遍历对角线,我们可以使用O(N^2) 搜索空间(请注意,总共有O(N^2) 的球,所以为了做得更好,我们不能检查所有条目)进行简单的递归。比方说西南。

           \ jth NW diagonal
            x
           x o
          A o x
         x o x o
        x o x B x / ...etc
       C o x o x o / 3rd iteration
      x o D o E F x / 2nd iteration
     x o x o x o x o / 1st iteration (ith SW diagonal)
    x o x o x o x G x
               / / / \
    

    沿着西南对角线的每个选择,都会限制所有其余的选择和总和低于西北对角线(例如,能够选择 E 意味着我们在之前的迭代中只选择了 FG 对角线并选择它将限制 AE 对角线以下的所有后续选择。

    假设我们将西南对角线标记为i,将我们的西北边界标记为j,并有一个函数sum_northwestO(1) 中计算(使用前缀和)并将一个西北对角线和一个西南边界作为参数.那么,如果f(i, j) 代表到ith 西南列的最优选择,并且西北方向为j

    f(i, j) = max(
      // Skip choosing from
      // this southwest diagonal
      f(i - 1, j),
    
      // Choose this northwest diagonal
      // on this southwest diagonal
      sum_northwest(j - 1, i) + f(i - 1, j - 1),
    
      // Choose an earlier northwest diagonal,
      // but then we are obliged to also
      // include this northwest diagonal
      sum_northwest(j - 1, i) + f(i, j - 1)
    )
    

    时间复杂度为O(|I| * |J|),假设我们正在列出结果。

    JavaScript 代码(未优化):

    function sum_northwest(M, j, i){
      return M[j].slice(0, i + 1)
        .reduce((a, b) => a + b, 0)
    }
    
    function f(M, i, j){
      if (i < 0 || j < 1 || i >= M[M.length-j].length)
        return 0
    
      let this_northwest =
        sum_northwest(M, M.length - j, i)
    
      return Math.max(
        f(M, i - 1, j),
        this_northwest + f(M, i - 1, j - 1),
        this_northwest + f(M, i, j - 1)
      )
    }
    
    var M = [
      [ 3, 3,-8, 7],
      [-5, 2,-2],
      [-8, 9],
      [ 3]
    ]
    
    console.log(f(M, 3, 4))
    
    M = [
      [-2,-10],
      [ 1]
    ]
    
    console.log(f(M, 1, 2))
    
    M = [
      [ 1, 3, 1],
      [-5,-4],
      [ 6]
    ]
    
    console.log(f(M, 2, 3))

    【讨论】:

      【解决方案3】:
           O
          /\
         /  \
       A/    \B
       / \  / \
      /   \/   \
      C    M    D
      

      假设我们在 M 拿了一个球,那么我们还需要从 MAOB 区域拿走所有的球。我们剩下 2 个三角形:CAM 和 MBD,我们需要从这两个三角形中选择球来最大化点。这是同样的问题,但输入较小。 所以我们在栈中所有子三角形的集合上定义值函数

      C[i,j,h] = 一个子三角形的最大点数,顶部在(i,j),高度=h(我在这里使用直角三角形来说明,因为它更容易绘制)

      |\
      | \
      |  \
      |   \
      | A  \
      | |\  \
      | | \  \
      | |  \  \
      | |D  \E \
      | |\  |\  \
      | | \ | \  \
      | |__\|__\  \
      | B   M   C  \
      |_____________\
      
      A = (i,j) 
      B = (i+h, j)
      C = (i+h, j+h)
      M = (i+h, j+k)
      D = (i, j+h-k)
      E = (i+k, j+k)
      

      递归公式:

      C[i,j,h] = max(
         C[i,j,h-1] // pick no ball at line i+h
         C[i,j+h-k,k] + C[i+k,j+k,h-k] + sum(ADME) for k from 0 to h // if pick a ball at (i+h, j+k)
      )
      

      【讨论】:

      • 我认为它真的很接近,只是你可以从每一行中选择多个球,而你的递归公式并没有真正解决这个问题。
      • 请在给定示例上添加时间复杂度和运行示例。我认为这真的会改善你的答案:)
      【解决方案4】:

      我将展示O(N log N) 时间复杂度和O(N) 内存复杂度的解决方案。

      首先,您是对的,我们将使用动态编程方法。我们将使用的数据结构将是一个相同大小的三角形。新三角形中的每个球都将是竞争对手的价值,如果它拿走了那个球。我们可以在O(N) 时间构建它 - 从上到下。

      现在我们需要注意一些有趣的说法:新三角形中值最高的球将被拿走(除非它是负数,在这种情况下我们不会拿走任何东西)。
      证明:(很简单)如果不取,那么我们可以取它并且必然得到更高的总值。

      另一个有趣的说法:下面的所有球(如果被拿走,就会拿走)数据结构中得分最高的球 - 不会被拿走。
      证明:如果他们被拿走,则意味着他们的价值是正的,没有最高价值的球,这意味着他们的价值更高,这是不可能的。

      现在进入算法:

      Build the data structure.
      Set of all taken items = {}
      while there are positive elements in the structure:
          Take highest one - put in the set.
          make all below it 0, and put then in the set.
          make all above it negative infinity.
          Rebuild the data structure for the rest.
      Return the set
      

      这将永远是最优的——我们拿走价值最高的球,我们永远不会错过一个对我们有帮助的球,而且由于我们重建了数据结构,我们永远不会错过其他球。

      内存复杂度很简单:O(N) 用于数据结构。
      时间复杂度很棘手:创建数据结构是O(N),每次在循环中我们删除至少一半的元素并且我们不会重新计算它们,因此迭代次数将在N 所以O(N log N) 中是对数的.

      【讨论】:

      • 我不确定我是否遵循。您能否举一个输入非常少的示例?
      • 另外,根据问题描述,当有 O(N^2) 个球时,我看不出 O(N log N) 是如何可能的。
      • 正确,我将 N 称为球数,我猜它只是 O(N^2 log N)
      猜你喜欢
      • 2019-06-19
      • 1970-01-01
      • 2016-06-29
      • 2019-04-17
      • 1970-01-01
      • 2021-12-01
      • 2017-11-29
      • 2013-03-01
      • 1970-01-01
      相关资源
      最近更新 更多