编程问题 - 积木游戏

发布于 2024-09-29 06:02:27 字数 850 浏览 1 评论 0原文

也许您会知道如何解决以下问题

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

每个测试用例都以整数 1 ≤ C ≤ 100 开头。下一行由 c 个整数组成。第 i 个整数 1 ≤ leni ≤ 750 表示第 i 个颜色的长度。下一行是正整数 N ≤ 10^15。

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

它可以推导为矩阵求幂问题,可以使用 Coppersmith-Winograd 算法和快速求幂。但似乎需要更有效的算法,因为 Coppersmith-Winograd 意味着一个很大的常数因子。您还有其他想法吗?这可能是一个数论或分治问题

maybe you would have an idea on how to solve the following problem.

John decided to buy his son Johnny some mathematical toys. One of his most favorite toy is blocks of different colors. John has decided to buy blocks of C different colors. For each color he will buy googol (10^100) blocks. All blocks of same color are of same length. But blocks of different color may vary in length.
Jhonny has decided to use these blocks to make a large 1 x n block. He wonders how many ways he can do this. Two ways are considered different if there is a position where the color differs. The example shows a red block of size 5, blue block of size 3 and green block of size 3. It shows there are 12 ways of making a large block of length 11.

Each test case starts with an integer 1 ≤ C ≤ 100. Next line consists c integers. ith integer 1 ≤ leni ≤ 750 denotes length of ith color. Next line is positive integer N ≤ 10^15.

This problem should be solved in 20 seconds for T <= 25 test cases. The answer should be calculated MOD 100000007 (prime number).

It can be deduced to matrix exponentiation problem, which can be solved relatively efficiently in O(N^2.376*log(max(leni))) using Coppersmith-Winograd algorithm and fast exponentiation. But it seems that a more efficient algorithm is required, as Coppersmith-Winograd implies a large constant factor. Do you have any other ideas? It can possibly be a Number Theory or Divide and Conquer problem

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

唯憾梦倾城 2024-10-06 06:02:27

首先注意你所拥有的每种颜色的块数是一个完整的红鲱鱼,因为 10^100 >总是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 code> 元素,这意味着 solve(n) 的非查找位被调用的次数大约相同。

Firstly note the number of blocks of each colour you have is a complete red herring, since 10^100 > N always. So the number of blocks of each colour is practically infinite.

Now notice that at each position, p (if there is a valid configuration, that leaves no spaces, etc.) There must block of a color, c. There are len[c] ways for this block to lie, so that it still lies over this position, p.

My idea is to try all possible colors and positions at a fixed position (N/2 since it halves the range), and then for each case, there are b cells before this fixed coloured block and a after this fixed colour block. So if we define a function ways(i) that returns the number of ways to tile i cells (with ways(0)=1). Then the number of ways to tile a number of cells with a fixed colour block at a position is ways(b)*ways(a). Adding up all possible configurations yields the answer for ways(i).

Now I chose the fixed position to be N/2 since that halves the range and you can halve a range at most ceil(log(N)) times. Now since you are moving a block about N/2 you will have to calculate from N/2-750 to N/2-750, where 750 is the max length a block can have. So you will have to calculate about 750*ceil(log(N)) (a bit more because of the variance) lengths to get the final answer.

So in order to get good performance you have to through in memoisation, since this inherently a recursive algorithm.

So using Python(since I was lazy and didn't want to write a big number class):

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])

Note I haven't exhaustively tested this, but I'm quite sure it will meet the 20 second time limit, if you translated this algorithm to C++ or somesuch.

EDIT: Running a test with N = 10^15 and a block with length 750 I get that memoise contains about 60000 elements which means non-lookup bit of solve(n) is called about the same number of time.

娇妻 2024-10-06 06:02:27

需要注意的是:在 c=2、len1=1、len2=2 的情况下,答案将是第 N 个斐波那契数,并且斐波那契数以黄金比例 phi 的增长因子(大约)呈指数增长〜1.61803399。对于
巨大的值 N=10^15,答案将约为 phi^(10^15),一个巨大的数字。答案会有存储
要求约为 (ln(phi^(10^15))/ln(2)) / (8 * 2^40) ~ 79 TB。因为您甚至无法访问 79
20 秒内传输 TB 字节,您不太可能满足这种特殊情况下的速度要求。

当 C 不太大并且 leni 对于所有 i 来说都很大时,您的希望就最大了。在这种情况下,答案将
仍随 N 呈指数增长,但增长因子可能小得多。

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

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

A word of caution: In the case c=2, len1=1, len2=2, the answer will be the N'th Fibonacci number, and the Fibonacci numbers grow (approximately) exponentially with a growth factor of the golden ratio, phi ~ 1.61803399. For the
huge value N=10^15, the answer will be about phi^(10^15), an enormous number. The answer will have storage
requirements on the order of (ln(phi^(10^15))/ln(2)) / (8 * 2^40) ~ 79 terabytes. Since you can't even access 79
terabytes in 20 seconds, it's unlikely you can meet the speed requirements in this special case.

Your best hope occurs when C is not too large, and leni is large for all i. In such cases, the answer will
still grow exponentially with N, but the growth factor may be much smaller.

I recommend that you first construct the integer matrix M which will compute the (i+1,..., i+k)
terms in your sequence based on the (i, ..., i+k-1) terms. (only row k+1 of this matrix is interesting).
Compute the first k entries "by hand", then calculate M^(10^15) based on the repeated squaring
trick, and apply it to terms (0...k-1).

The (integer) entries of the matrix will grow exponentially, perhaps too fast to handle. If this is the case, do the
very same calculation, but modulo p, for several moderate-sized prime numbers p. This will allow you to obtain
your answer modulo p, for various p, without using a matrix of bigints. After using enough primes so that you know their product
is larger than your answer, you can use the so-called "Chinese remainder theorem" to recover
your answer from your mod-p answers.

魂ガ小子 2024-10-06 06:02:27

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

我想提出一些改进,以进一步加快速度:

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

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

  3. 模运算的成本很高,乘法的成本也较低。通过使用位置环切换颜色环,可以将乘法和模数的数量减少大约 C=100 倍。这允许您在进行乘法和取模之前将多次调用solve() 的返回值相加。

测试解决方案性能的一个好方法是使用病态案例。以下可能尤其令人畏惧:长度 10^15、C=100、素数块大小。

希望这有帮助。

I'd like to build on the earlier @JPvdMerwe solution with some improvements. In his answer, @JPvdMerwe uses a Dynamic Programming / memoisation approach, which I agree is the way to go on this problem. Dividing the problem recursively into two smaller problems and remembering previously computed results is quite efficient.

I'd like to suggest several improvements that would speed things up even further:

  1. Instead of going over all the ways the block in the middle can be positioned, you only need to go over the first half, and multiply the solution by 2. This is because the second half of the cases are symmetrical. For odd-length blocks you would still need to take the centered position as a seperate case.

  2. In general, iterative implementations can be several magnitudes faster than recursive ones. This is because a recursive implementation incurs bookkeeping overhead for each function call. It can be a challenge to convert a solution to its iterative cousin, but it is usually possible. The @JPvdMerwe solution can be made iterative by using a stack to store intermediate values.

  3. Modulo operations are expensive, as are multiplications to a lesser extent. The number of multiplications and modulos can be decreased by approximately a factor C=100 by switching the color-loop with the position-loop. This allows you to add the return values of several calls to solve() before doing a multiplication and modulo.

A good way to test the performance of a solution is with a pathological case. The following could be especially daunting: length 10^15, C=100, prime block sizes.

Hope this helps.

为你鎻心 2024-10-06 06:02:27

在上面的答案中,

    ans += 1
    ans %= 100000007

如果没有一般模数,可能会更快:

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

In the above answer

    ans += 1
    ans %= 100000007

could be much faster without general modulo :

    ans += 1
    if ans == 100000007 then ans = 0
余生一个溪 2024-10-06 06:02:27

请参阅 TopCoder 线程了解解决方案。没有人能够足够接近地在该线程中找到答案。

Please see TopCoder thread for a solution. No one was close enough to find the answer in this thread.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文