编程问题 - 块游戏

时间:2010-10-29 00:12:03

标签: algorithm puzzle matrix-multiplication

也许您会对如何解决以下问题有所了解。problem

  约翰决定给儿子约翰尼买些数学玩具。他最喜欢的玩具之一是不同颜色的块。约翰决定购买不同颜色的C块。对于每种颜色,他将购买googol(10 ^ 100)块。所有相同颜色的块都具有相同的长度。但是不同颜色的块的长度可能不同。   Jhonny决定使用这些块来制作一个大的1 x n块。他想知道他能做多少方法。如果存在颜色不同的位置,则认为两种方式不同。该示例显示了一个大小为5的红色块,大小为3的蓝色块和大小为3的绿色块。它显示有12种方法可以生成一个长度为11的大块。

     

每个测试用例以1≤C≤100的整数开始。下一行包含c个整数。 ith整数1≤leni≤750表示第i种颜色的长度。下一行是正整数N≤10^ 15.

对于T <= 25个测试用例,该问题应在20秒内解决。应该计算答案MOD 100000007(素数)。

可以推导出矩阵求幂问题,它可以使用Coppersmith-Winograd算法和快速求幂在O(N ^ 2.376 * log(max(leni)))中相对有效地求解。但似乎需要更高效的算法,因为Coppersmith-Winograd意味着一个很大的常数因子。你还有其他建议吗?它可能是一个数论或分而治之的问题

5 个答案:

答案 0 :(得分:5)

首先请注意,每种颜色的块数是完整的红色鲱鱼,因为10 ^ 100&gt; N总是。因此,每种颜色的块数实际上是无限的。

现在请注意,在每个位置p(如果有有效配置,不留空格等)必须阻止颜色c。此块有len[c]种方式,因此它仍位于此位置p上。

我的想法是尝试所有可能的颜色和位置在固定位置(N/2,因为它将范围减半),然后对于每种情况,在此固定颜色块之前有b个单元格在此固定颜色块之后a。因此,如果我们定义一个函数ways(i),它返回平铺i个单元格的方式(ways(0)=1)。然后,在一个位置上用固定颜色块平铺多个单元格的方法的数量是ways(b)*ways(a)。添加所有可能的配置会产生ways(i)的答案。

现在我选择固定位置为N/2,因为它将范围减半,您可以将范围减半最多ceil(log(N))次。现在,由于您要移动关于N/2的块,您必须从N/2-750计算到N/2-750,其中750是块可以拥有的最大长度。所以你必须计算750*ceil(log(N))(因为方差而多一点)的长度来得到最终的答案。

因此,为了获得良好的性能,您必须通过备忘,因为这本身就是一种递归算法。

所以使用Python(因为我很懒,不想写一个大数字类):

T = int(raw_input())

for case in xrange(T):
    #read in the data
    C = int(raw_input())
    lengths = map(int, raw_input().split())
    minlength = min(lengths)
    n = int(raw_input())

    #setup memoisation, note all lengths less than the minimum length are
    #set to 0 as the algorithm needs this
    memoise = {}
    memoise[0] = 1
    for length in xrange(1, minlength):
       memoise[length] = 0

    def solve(n):
        global memoise
        if n in memoise:
            return memoise[n]

        ans = 0
        for i in xrange(C):
            if lengths[i] > n:
                continue
            if lengths[i] == n:
                ans += 1
                ans %= 100000007
                continue 
            for j in xrange(0, lengths[i]):
                b = n/2-lengths[i]+j
                a = n-(n/2+j)
                if b < 0 or a < 0:
                    continue
                ans += solve(b)*solve(a)
                ans %= 100000007
        memoise[n] = ans
        return memoise[n]
    solve(n)
    print "Case %d: %d" % (case+1, memoise[n])

注意我没有对此进行详尽的测试,但我确信它会满足20秒的时间限制,如果您将此算法转换为C ++或其他类似的。

编辑:使用N = 10^15和长度为750的块运行测试我得到memoise包含大约60000个元素,这意味着{{1}的非查找位调用大约相同的时间。

答案 1 :(得分:2)

提醒一句:在c = 2的情况下,len1 = 1,len2 = 2,答案将是第N个Fibonacci数,并且Fibonacci数以指数增长(近似)为黄金的增长因子比率,phi~1.61803399。为了 巨大的价值N = 10 ^ 15,答案将是关于phi ^(10 ^ 15),一个巨大的数字。答案将有存储空间 对(ln(phi ^(10 ^ 15))/ ln(2))/(8 * 2 ^ 40)~79太字节的要求。因为你甚至无法访问 79 在20秒内,太字节不太可能在这种特殊情况下满足速度要求。

当C不是太大时,你的最大希望就发生了,leni对我来说都很大。在这种情况下,答案会 仍然以N为指数增长,但增长因子可能要小得多。

我建议你先构造整数矩阵M,它将计算(i + 1,...,i + k) 基于(i,...,i + k-1)项的序列中的术语。 (只有这个矩阵的第k + 1行很有意思)。 “手动”计算前k个条目,然后基于重复的平方计算M ^(10 ^ 15) 技巧,并将其应用于术语(0 ... k-1)。

矩阵的(整数)条目将呈指数级增长,可能太快而无法处理。如果是这种情况,请执行此操作 对于几个中等大小的素数p,计算结果非常相似,但是模p。这将允许您获得 你的答案模p,对于各种p,不使用bigints矩阵。使用足够的素数以便您了解他们的产品 比你的答案大,你可以用所谓的“中国剩余定理”来恢复 你的mod-p答案的答案。

答案 2 :(得分:2)

我想在早期的@JPvdMerwe解决方案的基础上进行一些改进。在他的回答中,@ JPDMerwe使用动态编程/记忆方法,我同意这是解决这个问题的方法。将问题递归地划分为两个较小的问题并记住先前计算的结果是非常有效的。

我想提出一些可以进一步提高速度的改进措施:

  1. 除了可以定位中间块的所有方式,你只需要越过前半部分,然后将解决方案乘以2.这是因为后半部分是对称的。对于奇数长度的块,您仍然需要将居中位置作为单独的情况。

  2. 通常,迭代实现可以比递归实现快几个数量级。这是因为递归实现会导致每个函数调用的簿记开销。将解决方案转换为迭代表兄弟可能是一个挑战,但通常是可能的。 @JPvdMerwe解决方案可以通过使用堆栈来存储中间值来进行迭代。

  3. 模数运算是昂贵的,并且在较小程度上是乘法运算。通过用位置环切换颜色循环,可以将乘法和模数的数量减少大约C = 100。这允许您在执行乘法和模数之前将几个调用的返回值添加到solve()。

  4. 测试解决方案性能的一个好方法是使用病态案例。以下可能特别令人生畏:长度10 ^ 15,C = 100,主要块大小。

    希望这有帮助。

答案 3 :(得分:0)

在上面的答案中

    ans += 1
    ans %= 100000007
没有通用模数,

会快得多:

    ans += 1
    if ans == 100000007 then ans = 0

答案 4 :(得分:0)

请参阅TopCoder thread了解解决方案。没有人足够接近在这个帖子中找到答案。