World's most popular travel blog for travel bloggers.

Dynamic programming with large number of subproblems

, , No Comments
Problem Detail: 

Dynamic programming with large number of subproblems. So I'm trying to solve this problem from Interview Street:

Grid Walking (Score 50 points)
You are situated in an $N$-dimensional grid at position $(x_1,x_2,\dots,x_N)$. The dimensions of the grid are $(D_1,D_2,\dots,D_N$). In one step, you can walk one step ahead or behind in any one of the $N$ dimensions. (So there are always $2N$ possible different moves). In how many ways can you take $M$ steps such that you do not leave the grid at any point? You leave the grid if for any $x_i$, either $x_i \leq 0$ or $x_i > D_i$.

My first try was this memoized recursive solution:

def number_of_ways(steps, starting_point):     global n, dimensions, mem     #print steps, starting_point     if (steps, tuple(starting_point)) in mem:         return mem[(steps, tuple(starting_point))]     val = 0     if steps == 0:         val = 1     else:         for i in range(0, n):             tuple_copy = starting_point[:]             tuple_copy[i] += 1             if tuple_copy[i] <= dimensions[i]:                 val += number_of_ways(steps - 1, tuple_copy)             tuple_copy = starting_point[:]             tuple_copy[i] -= 1             if tuple_copy[i] > 0:                 val += number_of_ways(steps - 1, tuple_copy)     mem[(steps, tuple(starting_point))] = val     return val 

Big surprise: it fails for a large number of steps and/or dimensions due to a lack of memory.

So the next step is to improve my solution by using dynamic programming. But before starting, I'm seeing a major problem with the approach. The argument starting_point is an $n$-tuple, where $n$ is as large as $10$. So in fact, the function could be number_of_ways(steps, x1, x2, x3, ... x10) with $1 \leq x_i \leq 100$.

The dynamic programming problems I've seen in textbooks almost all have twp variables, so that only a two-dimensional matrix is needed. In this case, a ten-dimensional matrix would be needed. So $100^{10}$ cells in total.

With 2-D matrixes in dynamic programming, usually only the previous row of calculations is needed for the next calculation, hence reducing the spatial complexity from $mn$ to $\min(m,n)$. I'm not sure how I would do the same in this case. Visualizing a table isn't feasible, so the answer would have to come directly from the recursion above.

UPDATE

Using Peter Shor's suggestions, and making some minor corrections, notably the need to keep track of position in the $W(i, t_i)$ function, and rather than only splitting dimensions into two sets A and B, doing the splitting recursively, effectively using a divide-and-conquer method, until a base case is reached where only one dimension is in the set.

I came up with the following implementation, which passed all tests below the maximum execution time:

def ways(di, offset, steps):     global mem, dimensions     if steps in mem[di] and offset in mem[di][steps]:         return mem[di][steps][offset]     val = 0     if steps == 0:         val = 1     else:         if offset - 1 >= 1:             val += ways(di, offset - 1, steps - 1)         if offset + 1 <= dimensions[di]:             val += ways(di, offset + 1, steps - 1)     mem[di][steps][offset] = val     return val   def set_ways(left, right, steps):     # must create t1, t2, t3 .. ti for steps     global mem_set, mem, starting_point     #print left, right     #sleep(2)     if (left, right) in mem_set and steps in mem_set[(left, right)]:         return mem_set[(left, right)][steps]     if right - left == 1:         #print 'getting steps for', left, steps, starting_point[left]         #print 'got ', mem[left][steps][starting_point[left]], 'steps'         return mem[left][steps][starting_point[left]]         #return ways(left, starting_point[left], steps)     val = 0     split_point =  left + (right - left) / 2      for i in xrange(steps + 1):         t1 = i         t2 = steps - i         mix_factor = fact[steps] / (fact[t1] * fact[t2])         #print "mix_factor = %d, dimension: %d - %d steps, dimension %d - %d steps" % (mix_factor, left, t1, split_point, t2)         val += mix_factor * set_ways(left, split_point, t1) * set_ways(split_point, right, t2)     mem_set[(left, right)][steps] = val     return val  import sys from time import sleep, time  fact = {} fact[0] = 1 start = time() accum = 1 for k in xrange(1, 300+1):     accum *= k     fact[k] = accum #print 'fact_time', time() - start  data = sys.stdin.readlines() num_tests = int(data.pop(0)) for ignore in xrange(0, num_tests):     n_and_steps = data.pop(0)     n, steps = map(lambda x: int(x), n_and_steps.split())     starting_point = map(lambda x: int(x), data.pop(0).split())     dimensions = map(lambda x: int(x), data.pop(0).split())     mem = {}     for di in xrange(n):         mem[di] = {}         for i in xrange(steps + 1):             mem[di][i] = {}             ways(di, starting_point[di], i)     start = time()     #print 'mem vector is done'     mem_set = {}     for i in xrange(n + 1):         for j in xrange(n + 1):             mem_set[(i, j)] = {}     answer = set_ways(0, n, steps)     #print answer     print answer % 1000000007     #print time() - start 
Asked By : Alexandre

Answered By : Peter Shor

The different dimensions are independent. What you can do is compute, for each dimension j, how many different walks there are in just that dimension which take $t$ steps. Let us call that number $W(j,t)$. From your question, you already know how to compute these numbers with dynamic programming.

Now, it's easy to count the number of walks that take $t_i$ steps in dimension $i$. You have $N \choose t_1,t_2, \ldots, t_M$ ways of interspersing dimensions so that the total number of steps taken in dimension $i$ is $t_i$, and for each of these ways you have $\Pi_1^N W(i,t_i)$ walks. Sum over these to get $$ \sum_{t_1+t_2+\ldots+t_N=M}{M \choose t_1,t_2,\ldots,t_N}\ \Pi_{i=1}^N W(i,t_i). $$ Now, the memory is under control, since you only need to remember the values $W(j,t)$. The time grows superpolynomially for large $N$, but most computers have a lot more time than memory.

You can do even better. Recursively divide the dimensions into two subsets, $A$ and $B$, and compute how many walks there are using just the dimensions in subset $A$, and just those in $B$. Call these numbers $W_A(t)$ and $W_B(t)$, respectively. You get the total number of walks by

$$ \sum_{t_1 + t_2 = M} {M \choose t_1} \, W_A(t_1) W_B(t_2). $$

Best Answer from StackOverflow

Question Source : http://cs.stackexchange.com/questions/4941

0 comments:

Post a Comment

Let us know your responses and feedback