使用动态规划创建最大配置

11
我一直在尝试解决下面放置的问题。我有几个想法已经尝试过了。最初,我考虑选择N元组的所有组合并对它们进行排序,但是实现很丑陋且速度太慢了。我认为这个问题可以采用动态规划方法来解决。我遇到的问题是如何创建配置文件。之后,我认为我知道如何解决这个问题。
问题陈述:
给定高度H(1 <= H <= 1,000,000,000),我们需要找到一系列高度从N元组中大于或等于H。有一些条件: 每个N元组都有权重、高度和强度。 元组的强度表示可以放在该元组上方的总重量的最大值。
问题要求找到堆栈的最大安全值。安全值是可以添加的重量量,而不超过较低元组的强度。如果不可能,只需打印出-1。
输入:
输入的第一行包含N和H。
接下来的N行输入每个元组的描述,给出它的高度、重量和强度。所有的数都是正整数,最多为10亿。
示例输入:
4 10
9 4 1
3 3 5
5 5 10
4 4 5
示例输出:
2

1
请更好地解释问题。 H 扮演什么角色? 你所说的堆栈是什么? 您可以尝试为示例输入解释这些内容,并展示示例输出是正确答案。 - Pradhan
2
你说得对,这个问题确实有一个动态规划的解法。首先,将元组按强度从高到低排序。现在,当你建立堆栈时,你总是将当前元组放在堆栈顶部。你永远不需要保留那些已经有另一个至少和当前元组一样高,并且断点至少和当前元组一样低的堆栈。 - btilly
每个元组只能使用一次,对吗? - MikeMB
2
元组的数量级是多少? - MikeMB
当你说“给定一个高度 H”时,H 是元组列表的最大高度吗? - Rerito
您能指出问题的来源并为您的来源提供适当的引用吗? - D.W.
1个回答

9

好的,既然没有其他人发布解决方案,我来试试。

给定两个元组,t1 = (h1, w1, s1)t2 = (h2, w2, s2),我们可以将它们组合成 [t1 over t2][t2 over t1]。在每种情况下,我们都可以将结果视为另一个元组;例如,

t3 = [t1 over t2] = (h1 + h2, w1 + w2, min(s1, s2 - w1))

由两个元组组合而成的结果元组的强度不能高于两个组成元组中任意一个的强度,并且底部元组的强度会因顶部元组的重量而减少;结果强度可以为负数。

无论组合顺序如何,结果元组的高度和重量相同;但是,结果强度可能因顺序不同而不同。我们对最大强度的元组感兴趣,因此我们取两种可能值中的最大值。鉴于以上情况,让我们将组合定义为

t1 + t2 = (h1 + h2, w1 + w2, max(min(s1, s2 - w1), min(s2, s1 - w2)))

生成的元组可以与其他元组组合,以此类推。
我们需要找到高度至少为H的所有生成元组中的最大强度,因为问题中要求的最大安全值实际上是生成元组的强度。
因此,我们可以将起始最大强度设置为-1,开始组合元组,并且每当我们找到一个高度为H或更高的元组时,如果该元组的强度更大,则更新当前最大强度。 规则1:生成元组的强度不能高于两个组成元组的强度之一,因此,在组合元组时,每当我们发现一个强度小于或等于当前最大值的元组时,我们可以丢弃它,因为其中任何一个元组都不可能比当前最大值更强。 规则1a:我们甚至可以丢弃用于更新当前最大强度的元组,因为问题并未要求我们返回元组本身,只需返回最大值,而该元组在任何其他组合中都不会产生更好的最大值。

规则2:现在,让我们从上到下看。任何由n = 2k个元组组成的堆栈都可以看作是由两个元组组成,每个元组由一个由k个元组组成的堆栈组成;对于n = 2k + 1,这两个堆栈的大小分别为kk + 1

因此,我们按顺序构建:

  1. 初始元组列表;
  2. 由两个堆栈产生的元组列表;
  3. 由三个堆栈产生的元组列表,元组由列表[1]中的一个元组和列表[2]中的一个元组组成(所有组合,除了使用主元组两次的那些组合);
  4. 由四个堆栈产生的元组列表,元组由两个元组[2]中的元组组成(再次,所有组合,除了使用主元组两次的那些组合);

依此类推,直到N。在构建每个列表时,我们根据上面的规则1进行修剪。

< p > 规则1b:每当最大强度更新时,所有现有列表都应修剪掉强度小于或等于新最大值的元组。这可以立即完成,也可以在将这些元组作为组成新元组的一部分时进行惰性处理。

就算法描述而言,我认为就是这样。

在实现方面,我会将实际元组存储为std::tuplestruct,并加以改进:对于每个生成的元组,您需要存储它是由哪些原始元组构建而成的列表;我会使用std::vector<std::size_t>(其中包含第一个列表中主元组的索引)来实现,因为您可以使用std::find_first_of来排除使用主元组两次的组合,甚至更好的方法是,如果保持列表排序,则可以使用std::set_intersection

对于每个级别的列表,也使用std::vector

实际的C++代码当然是你的工作。


注意: 这里描述的算法具有非常糟糕的最坏复杂度特性。对于这个解决方案来说,最坏情况意味着:大量的N,大量的H,与H相比,元组高度较小,元组重量较小。在这种情况下,除非很晚才能出现上述规则中所描述的任何修剪操作,否则我们会出现组合爆炸。

然而,在我看来,对于更均匀分布高度、重量和强度的更“有趣”的情况(类似于给定的示例),我认为这个解决方案表现得相当不错,甚至与经典的动态规划解决方案相比也不遑多让,这种情况下,后者可能是类似于整数背包解决方案的一个条件被反转(还没有真正思考过)。

以后如果有时间做一些实际测量,只是出于好奇,我可能会回到这个问题。


你成功理解了问题,并在回答中付出了如此多的心血。这值得点赞...可惜我只得到了一个。 - Rerito

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接