解释Cat / Egg投掷问题的这个O(n log n)算法

时间:2011-01-15 10:22:40

标签: algorithm

This problem(为了确定这样一只猫能活下来的最大楼层,你需要从建筑物中扔出多少只猫。实际上非常残忍),已经接受了O(n ^ 3)的答案)复杂性。该问题等同于此Google Code Jam,对于N = 2000000000,该问题应该是可解的。

似乎O(n ^ 3)解决方案不足以解决它。 从查看in the solutions page开始,jdmetz的解决方案(#2)似乎是O(n log n)。 我不太了解算法。有人可以解释一下吗?

修改

2 个答案:

答案 0 :(得分:8)

顶级解决方案实际上是O(D*B)(使用问题的术语),但作者注意到,对于大多数DB,答案将大于2^32,因此{ {1}}可以退回 因此,他引入了等于1100的-1 - 最重要的maxnD。 对于F,他立即返回-1。对于D, B = 10000,使用下面的递归公式。仅对于某些“角点值”,例如D, B = 100,使用直接公式。 (有关“直接配方”的详细信息,请参阅他的评论)

对于D = 10000, B = 2,作者在评论中提供了公式:D, B < maxn。此处的函数f(d,b) = f(d-1,b)+f(d-1,b-1)+1会返回最大楼层数,您可以使用不超过f次尝试且不超过d个鸡蛋来确定“断点”。
公式本身应该是不言自明的:无论我们在哪个楼层抛出第一个蛋,都有两个结果。它可能会破裂,然后我们可以检查下面的b楼层。或者它可以“生存”,然后我们可以检查上面的f(d-1, b-1)楼层。使用当前楼层,总共f(d-1, b)

现在,它可以很容易地编码为DP(动态编程)。如果你离开无穷大(f(d-1,b)+f(d-1,b-1)+1)检查,它会变得很干净。

2^32

当我们有数组 for (int i = 1; i < maxn; ++i) { for (int j = 1; j < maxn; ++j) { f[i][j] = f[i - 1][j - 1] + f[i - 1][j] + 1; } } 存储这些结果时,查找f[D][B]D'是二进制搜索。 (因为函数B'fd单调增长

PS虽然我在'猫'问题的答案中确实说过一个更快的解决方案,但这实际上比我的想法更酷:)感谢你和 sclo (作者)!

答案 1 :(得分:2)

由于问题被更正而严重编辑

考虑函数f(x,y),它给出了你可以测试的楼层数,限制为x抛出和y死亡。如果第一次投掷导致死亡,你有x-1投掷和y-1死亡,所以你可以检查f(x-1,y-1)楼层。如果第一次投掷没有导致死亡,你有x-1投掷和y死亡,所以你可以检查你扔掉的f(x-1,y)楼层。因此我们有递推f(x,y)= f(x-1,y-1)+ f(x-1,y)+ 1.基本条件是f(x,0)= 0(因为如果你甚至一次投掷你冒着死亡的风险,所以你不能投掷,甚至不能检查一楼),而f(1,x)= 1其中x> 0(你必须从一楼,因为如果你从较高的楼层扔出去并且死亡,你就没有结果。)

现在,考虑函数g(x,y)= SUM 1&lt; = r&lt; = y of x C r 。 (这是二项式系数,x选择r。我不认为此站点支持TeX表示法)。断言是f(x,y)= g(x,y)。要检查基本情况,请考虑g(x,0)是0个元素的总和,以便匹配;和g(1,y),其中y> 0表示 1 C 1 = 1.因此,只需检查g是否满足复发。

g(x-1,y-1)+ g(x-1,y)+ 1 = SUM 1&lt; = r&lt; = y-1 x-1 C r + SUM 1&lt; = r&lt; = y x-1 C r + 1

g(x-1,y-1)+ g(x-1,y)+ 1 = SUM 2&lt; = r&lt; = y x-1 C r-1 + SUM 1&lt; = r&lt; = y x-1 C r + 1

g(x-1,y-1)+ g(x-1,y)+ 1 = SUM 2&lt; = r&lt; = y x-1 C r-1 + SUM 1&lt; = r&lt; = y x-1 C r + x-1 C 0

g(x-1,y-1)+ g(x-1,y)+ 1 = SUM 1&lt; = r&lt; = y x-1 C r-1 + SUM 1&lt; = r&lt; = y x-1 C r

g(x-1,y-1)+ g(x-1,y)+ 1 = SUM 1&lt; = r&lt; = y( x-1 C r- 1 + x-1 C r

g(x-1,y-1)+ g(x-1,y)+ 1 = SUM 1&lt; = r&lt; = y( x C r

g(x-1,y-1)+ g(x-1,y)+ 1 = g(x,y)

QED

事实上,这可以通过将其视为组合问题直接推导出来。如果我们充分利用所获得的每一点信息,那么每个可能的生存或死亡序列对应于不同的最大楼层。例如。三次投掷,一次允许死亡,可能性是死亡;生存死亡;生存生存,死亡;或生存 - 生存 - 生存。但是,我们不得不打折没有死亡的情况,因为在这种情况下我们还没有确定底线。因此,如果我们有x次投掷和y允许死亡,我们可以有从1到y的实际死亡数r,并且对于每个我们有 x C r 的人序列。 (如果r = y那么y th 死亡后的任何“幸存者”实际上都是“没有扔掉”的。)

jdmetz的F解决方案包括评估g(D,B)。它不能在O(min(D,B))时间内完成,因为该总和没有封闭的超几何形式(这一事实可以用Gosper的算法证明)。

<强>附录 实际上,原始问题的所有三个部分都可以在线性时间内完成(假设乘法和算术是恒定时间操作,我们到目前为止 - 不是真的,但我们将其放在一边)。 jdmetz解决方案中的O(n lg n)运算是找到最小的D,使得f(D,B)> = F.

如果我们将原始复发f(D,B)= f(D-1,B-1)+ f(D-1,B)+ 1与g,f(D)形式的术语差异结合起来,B)= f(D,B-1)+ D C B 我们得到f(D,B)= 2 * f(D-1,B)+ 1 - D-1 C B 。然后我们可以使用 a C b = a-1 C b * a /(a - b)来从1循环到D。

    private static int d_opt(final long f, final int b)
    {
        int d = 0, dCb = 0;
        long f_db = 0;
        while (f_db < f)
        {
            dCb = (d == b) ? 1 : d * dCb / (d-b);
            f_db = 2 * f_db + 1 - dCb;
            d++;
        }
        return d;
    }