views:

415

answers:

5

Hi guys, 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

+5  A: 

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.

JPvdMerwe
I though about a very similar solution. There is something I don't understand in the answer. `So you will have to calculate about 750*ceil(log(N))`. Is it not `100*750^2*log^2(N)`? Note that in your code first you take each color and then each element which results in `100*750`. Additionally, when N is divided into 2 parts at step i, the number of different block lengths will be `i * 750` (for i >= 1) (for the first step [ i = 0 ] there is only one state - N), hence `log^2(N) * 750`. Overally this results in `100 * 750^2 * 60^2 ~ 200 * 10^9` (which is not enough to pass 1 max test)
Leonid
Actually the number of iterations is less by `60`. Overally this results in `100 * 750^2 * 60`. Having experimental result of `60000` elements we can replace `750*60` with `60000`. That results in `100 * 750 * 60000 = 4.5 * 10^9`. Assuming around 100 * 10^6 iterations per second this may well take around 30 seconds for the maximum test, given that MOD (quite a complex operation) will be the bottleneck for the CPU.
Leonid
I admit I misspoke a bit, what I mean is that you will only have to calculate `O(750*log(N))` elements in the `memoise` hash-map. The running time will be about `O(750*100*750*log(N))`. Thinking about it now it's a bit slow...
JPvdMerwe
The solution can be improved by doing DP precalculation for small N. That should be possible to precalculate around `10^6 items` in `10^8`. Reducing thus `log(10^15)` to `log(10^9)`. However it will still not be enough. Additionally, we can divide not by `2`, but say by `10`. Experimentally, that should be possible to estimate the optimal number of divisions. Increasing number of divisions we result in lesser number of states that need to be calculated.
Leonid
+2  A: 

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.

Josephine
Fibonacci numbers mod P can be calculated using the matrix form of Fibonacci in `O(Log(N))`, where `N = 10^15`. For `C > 2` the problem can be converted to matrix for and similar matrix exponentiation algorithm applied as you mentioned, but the size of the matrix is way too large even to make one iteration of multiplication. It can be as large as `750 x 750 - O(N^3)` for a single multiplication. It is also known that the answer needs to be calculated `mod 100000007 (prime number)`, hence Chinese remainder theorem is not applicable.
Leonid
I see. I suggest you include the mod 100000007 requirement in the problem statement above.
Josephine