堆叠瓷砖(硬算法)
Posted
技术标签:
【中文标题】堆叠瓷砖(硬算法)【英文标题】:Stacking Tiles (Hard algorithm) 【发布时间】:2015-04-21 18:06:26 【问题描述】:这是来自代码竞赛的问题,我发现想出任何可行的算法来解决它非常困难。所以我不是真的在寻找代码,而是在寻找如何解决它的逐步算法。
堆叠瓷砖
靠墙堆放瓷砖是 Bongani 最喜欢的消遣活动之一。他的瓷砖都具有相同的厚度,但各不相同 在宽度和高度。 Bongani 有 N 个瓷砖,必须在 根据一组规则给出的序列。他可以在上面放一块瓷砖 仅当它比先前堆叠的更窄时才在另一个顶部 瓦。 Bongani 可以将瓷砖旋转 90 度,以便 宽度变成高度,高度变成宽度。他也被允许 完全丢弃瓷砖。给定一个瓷砖列表,帮助 Bongani 找到 他可以建立的最高堆栈 该示例指定瓷砖 (3, 3), (12, 5), (5, 8), (6, 10)。为了获得最高的筹码,Bongani 忽略了 第一个图块 (3, 3),因为它小于下一个图块。他使用 下一个瓦片 (12, 5),宽度为 12,高度为 5。他用 接下来的两个瓷砖,宽度为 8,高度为 5,然后 宽度为 6,高度为 10。
我唯一能想到的就是获取所有可能的有效图块排列并找到最高排列。 确切的问题可以在这里找到http://www.olympiad.org.za/olympiad/wp-content/uploads/2013/01/2011PO-R2-Questions-0421.pdf(问题5)
【问题讨论】:
你明白我的回答了吗,还是我应该澄清什么? 【参考方案1】:下面是动态规划解决方案的概要:
您“从左到右移动”并且对于您找出的每个图块
使用这个不旋转的瓷砖我可以建造多高的塔 使用旋转的瓷砖我可以建造多高的塔 不使用这块瓷砖我能建多高的塔第一个关键观察是每个问题都可以递归回答(“如果根据我当前的选择更新当前宽度,我可以为剩余的瓷砖建造多高的塔?”)。伪代码:
maxHeight(tiles, currentWidth)
// Base case
if (tiles.isEmpty())
return 0; // no tiles -> maxHeight == 0
int h = 0;
currentTile = tiles[0]
remainingTiles = tiles[1...]
// Compute maxHeight for the case when not using current tile
h = max(h, maxHeight(remainingTiles, currentWidth)
// Compute maxHeight when using current tile
if (currentWidth > currentTile.width)
subHeight = maxHeight(remainingTiles, currentTile.width)
h = max(h, subHeight + currentTile.height)
// Compute maxHeight when using current tile rotated
if (currentWidth > currentTile.height)
subHeight = maxHeight(remainingTiles, currentTile.height)
h = max(h, subHeight + currentTile.width)
return h
第二个关键观察是maxHeight
的许多调用具有相同的参数,这意味着可以重用以前的计算。您可以使用记忆或制表(两者都是动态编程的变体)如果您选择使用制表矩阵,它看起来像这样:
M[tileN][width] = the height of the tower possible to build from
tileN onwards with width 'width'
(您可能注意到width
没有明确的上限。这可以通过在开始之前将所有值映射到1, 2, 3, ...
来解决。最大宽度将为2N。)
【讨论】:
你为什么要对图块进行排序?我想你想象了一个不同的更难的问题,你可以重新排列图块。 哦,我以为我可以按任何顺序使用它们...让我修改一下。 你说得对,它确实比我最初想象的要简单。 我很难理解递归,为什么 h 会改变 'int h = 0;'每次调用方法时都会运行,所以 h 一直在休息h
是一个局部变量。 h
变量的数量与堆栈上对 maxHeight
的调用一样多。【参考方案2】:
这是一个使用动态规划的二次时间算法。设 f(i) 是您可以使用原始方向的第 i 个块而不是以后的块构建的塔的最大高度。让 g(i) 是您可以在旋转第 i 个块且没有后续块的情况下可以构建的塔的最大高度。请注意,可以省略块,因此要计算 f(i),您必须比与该方向兼容的所有先前 f 和 g 值的最大值多 1,对于 g(i) 也是如此。最后,答案是所有 f(i) 和 g(i) 的最大值。
以下代码显示了 f 的代码。你可以类似地写 g ,或者修改它以获取另一个参数来判断块 i 是否处于原始方向。
public int f(int i)
if (i == 0)
return 1;
if (memoF[i] > 0)
return memoF[i];
int maxFound = 1; // using just this block is legal
for (int j = 0; j<i; j++)
if (widths[i] < widths[j])
maxFound = Math.max(f(j)+1,maxFound);
if (widths[i] < heights[j])
maxFound = Math.max(g(j)+1,maxFound);
memoF[i] = maxFound;
return memoF[i];
【讨论】:
这远高于我的工资等级:/。您能否尝试更简单地解释这一点。但是非常感谢您的回答。 @TamirShklaz:第一步可能是更仔细地阅读问题陈述。从你的 cmets 关于尝试每一个排列,听起来你认为你可以重新排序瓷砖,但问题陈述说你不能。所以没有可以尝试的排列。你知道什么是记忆化或动态规划吗? 我对动态编程知之甚少,对记忆化几乎一无所知。您知道有关此问题的任何指南或教程吗? 如果您使用 Google 动态编程,您应该会在以下热门搜索中找到:动态编程的 Wikipedia 页面和 CodeChef 动态编程的教程。请花一些时间学习这项重要且基本的技术,您应该了解竞争性编程,否则您将无法理解此处给出的答案。【参考方案3】:我认为这是使用回溯轻松有效解决问题的典型示例。
您只需按顺序尝试您可以做的第一件事,当您无法继续时,您就回去尝试以前没有尝试过的事情。只需谷歌“数独回溯”,就有很多页面解释了这一点。
在这种情况下,回溯的巨大优势在于,它“切断”了很多没有意义的场景,因此它比尝试检查每个可能的组合要有效得多。 (就像在数独中,通过回溯,大多数数独在 1000-10000 步内解决,这非常好,因为您可以编写的所有可能的数字组合约为 10^60)
【讨论】:
您是在建议指数时间算法?以上是关于堆叠瓷砖(硬算法)的主要内容,如果未能解决你的问题,请参考以下文章
堆叠抓取+深度学习基于深度学习+PPO深度强化学习的堆叠物体抓取算法的MATLAB仿真