我正在努力解决一些没有结果的旧编程竞赛的问题,所以我在这里寻求帮助。让我在下面描述一下。
首先,我们有1< = n < = 1000堆硬币,每个硬币包含至多10 ^ 9个硬币。然后在一次移动中我们选择最高堆栈(如果有许多具有最大数量的硬币,我们选择其中一个)并且假设它具有 m 硬币。然后我们将它分成两个堆栈,分别包含 m div 2和( m +1)div 2个硬币。我们进行移动,直到只有1枚硬币堆叠。
因此,此问题的输入是数字 n ,然后 n 数字描述这些堆栈的初始高度。然后存在数字1 <= q &lt; = 5 * 10 ^ 5,其表示查询的数量。每个查询由一个数字1&lt; = k &lt; = 10 ^ 9组成。所以我们分别有 q 查询:k_1,k_2,...,k_q,我们知道这些数字的总和小于或等于直到结束条件的可能移动数。对于数字k_1,我们必须在k_1移动后打印不同堆叠高度的数量。对于数字k_2,我们必须在下一步 k_2移动后打印不同堆叠高度的数量,依此类推。
如果没有可能的堆叠初始高度(10 ^ 9),这个问题会很容易。
对于示例,我们首先分别有3个堆叠,分别为9个,2个和8个硬币。然后对于查询输入(第一个数字是一个数字q):3,1,2,3,正确的输出是:4,3,2,因为在第一次移动后我们有堆栈:8,5,4,2,后三移动我们有:4,4,4,3,2(三个不同的高度),经过1 + 2 + 3 = 6次移动我们有:3,2,2,2,2,2,2,2,2。
有人可以帮忙吗?我怎么能快速解决这个问题? (一个测试文件和256MB RAM的标准时间限制为1秒,类似O(q log q)应该没问题。)
答案 0 :(得分:3)
你可以使用一个简单的平衡二叉搜索树和一个(略微优化的)游戏模拟来解决这个问题:
h
,将其计数器减1并将h/2
和(h+1) / 2
的计数器增加1。不要忘记删除计数器0的节点。一个重要的优化是你“快进”到下一个有趣的步骤。如果您有m
个堆叠高度h
,则可以通过移除节点h
并将m
添加到节点h/2
和{{}}来同时处理所有这些内容{1}}。
您可以这样做的原因是,单个堆栈可能创建的节点数量受(h+1) / 2
限制。2 * ceil(log_2(10^9)) = 60
的BBST中的节点数。 分析:您的二进制搜索树中最多会有k_i
个节点,其中O(log H * n)
是最大高度(在您的情况下为H
)。因此,对二叉搜索树的操作需要时间10^9
您最多需要O(log (log H * n)) = O(log log H + log n)
个操作,因为如果查询无法通过查看单个节点来解决,这意味着您从中删除至少一个节点BBST(并且只存在O(q + log H * n)
个。总的来说,最糟糕的情况是O(log H * n)
。
我认为实际上并不能实现,因为实际上由于堆栈之间的重叠,你肯定会有少于O((q + log H * n) * (log log H + log n))
个节点。
答案 1 :(得分:1)
如果你更仔细地观察这个问题,你会发现你最终在任何堆栈中都有大约log(n)个不同的堆栈,因为你将最高堆栈的数量除以2.
进行模拟,您可以批量进行大部分动作。例如
如果最高桩有100个硬币,并且有1000个这样的桩,你通过增加2000个50码的桩来进行1000次移动。
你将最终计算每个N尺寸桩的桩数。您应该使用支持这种逻辑的数据结构(例如键/值存储)
答案 2 :(得分:0)
不是一个完整的答案,但我看到问题的一个简化,这将使建模更容易:
你在那里有一个随意堆栈,它会混淆建模。你实际上并不需要它,堆栈是相同的。此外,如果你有n个相同高度的堆栈,你知道接下来的n个移动将拆分这些堆栈。
这也意味着您无需跟踪每个堆栈,只需跟踪您拥有的给定高度的堆栈数。
答案 3 :(得分:0)
让我们看看更精确地表达这个是否有帮助(我使用java,因为我很舒服,但你可以使用你需要的任何东西):
首先,我们有1&lt; = n&lt; = 1000堆硬币,每个硬币包含至多10 ^ 9个硬币。然后在一次移动中我们选择最高的堆叠(如果有许多具有最大数量的硬币,我们选择其中一个)并且假设它具有m个硬币。然后我们将它分成两个堆栈,分别包含m div 2和(m + 1)div 2个硬币。我们进行移动,直到只有1枚硬币堆叠。
所以我们有一个SortedSet<Integer> coinStacks
我们知道最多有1000个元素,最多10 ^ 9:
assert(coinStacks.size() >= 1, "Too few stacks");
assert(coinStacks.size() <= 1000, "Too many stacks");
assert(coinStacks.last() <= 100000000000, "Too large stacks");
我们还有一个功能:
void splitBiggestStack(SortedSet<Integer> coinStacks){
int coinStack = coinStacks.last();
coinStacks.remove(coinStack);
coinStacks.add(coinStack/2);
coinStacks.add(coinStack/2 + coinStack%2);
}
我们在coinStacks
上迭代地应用函数,直到只剩下1个堆栈:
while(coinStacks.last() > 1)
splitBiggestStack(coinStacks);
总而言之:
void iterativeCoinStackReduction(SortedSet<Integer> coinStacks){
assert(coinStacks.size() >= 1, "Too few stacks");
assert(coinStacks.size() <= 1000, "Too many stacks");
assert(coinStacks.last() <= 10000000000, "Too large stacks");
while(coinStacks.last() > 1)
splitBiggestStack(coinStacks);
}
void splitBiggestStack(SortedSet<Integer> coinStacks){
int coinStack = coinStacks.last();
coinStacks.remove(coinStack);
coinStacks.add(coinStack/2);
coinStacks.add(coinStack/2 + coinStack%2);
}
好的,下一段。
因此,此问题的输入是数字n,然后是n个数字,描述这些堆栈的初始高度。然后是数字1&lt; = q&lt; = 5 * 10 ^ 5,其表示查询的数量。每个查询由一个数字1&lt; = k&lt; = 10 ^ 9组成。所以我们分别有q个查询:k_1,k_2,...,k_q,我们知道这些数字的总和小于或等于直到结束条件的可能移动的数量。对于数字k_1,我们必须在k_1移动后打印不同堆叠高度的数量。对于数字k_2,我们必须在下一次k_2移动后打印不同堆叠高度的数量,依此类推。
所以我们有:
ArrayList<Integer> coinStackSplittingProblem(SortedSet<Integer> coinStacks, ArrayList<Integer> queries){
//n = coinStacks.size(), q = queries.size()
assert(queries.length >= 1, "Too few queries");
assert(queries.length <= 500000, "Too many queries");
for(int query:queries){
assert(query >= 1, "Too small query");
assert(query <= 1000000000, "Too large query");
}
assert(queries.stream().sum() <= coinStacks.stream().sum(), "Too large total query");
ArrayList<Integer> results = new ArrayList<>(queries.size());
int qSum = 0;
for(int query:queries){
int count = qSum;
qSum += query;
while(count++ < qSum)
splitBiggestCoinStack(coinStacks);
results.add(coinStacks.stream().distinct().count());
}
return results;
}
现在我们有了实际的代码,我们可以根据优化的黄金规则尝试优化它:(1)除非你需要,否则不要这样做,(2)不要这样做,(3) )在你有测试结果告诉你要优化什么之前不要这样做。
现在进行测试,找出需要优化的内容并进行优化;如果你有一台不错的计算机,这可能甚至不需要:1秒和256 MB的RAM应该是充足的。
(如果我正在对此进行优化,我猜主要的时间杀手是SortedSet
,我会用PriorityQueue
代替。)