具有大量子问题的动态编程


11

具有大量子问题的动态编程。因此,我正在尝试通过Interview Street解决此问题:

网格漫游(得分50分)
您位于维网格中,位置为。网格的尺寸为)。一步,您可以在维度中的任何一个维度上向前或向后走一步。(因此,总是有可能的不同动作)。您可以采用多少种方法采取步骤,以使您在任何时候都不会离开网格?如果有任何,则离开网格,或。X 1X 2... X Ñd 1d 2... d Ñ Ñ 2 Ñ 中号X X 0 X > d N(x1,x2,,xN)(D1,D2,,DNN2NMxixi0xi>Di

我的第一个尝试是这个记住的递归解决方案:

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

大惊喜:由于缺少内存,它在许多步骤和/或尺寸上失败了。

因此,下一步就是使用动态编程来改进我的解决方案。但是在开始之前,我发现该方法存在一个主要问题。参数starting_point是元组,其中等于。因此,实际上,该函数可能具有 。Ñ 10 1 X 100nn10number_of_ways(steps, x1, x2, x3, ... x10)1xi100

我在教科书中看到的动态编程问题几乎都包含twp变量,因此只需要一个二维矩阵。在这种情况下,将需要一个十维矩阵。因此总共有单元。10010

对于动态编程中的二维矩阵,通常仅需要前一行计算才能进行下一次计算,因此将空间复杂度从降低到。在这种情况下,我不确定该怎么做。可视化表格是不可行的,因此答案必须直接来自上面的递归。mnmin(m,n)

更新

根据彼得·索尔(Peter Shor)的建议,并进行一些较小的更正,尤其是需要跟踪函数中的位置,而不是仅将维度分为两组A和B,而是使用a进行递归拆分分而治之的方法,直到达到基本情况,即集合中只有一个维。W(i,ti)

我想出了以下实现,通过了所有测试,但未超过最大执行时间:

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

2
“它因大量步骤和/或尺寸而失败” –“失败”在这里是什么意思?
拉斐尔

1
欢迎!我将您的问题编辑为:a)使用正确的Markdown和LaTeX格式(请以后使用),以及b)删除多余的装订线。我们不在乎C代码的模糊性;请把自己限制在思想上,那是核心事物的伪代码。
拉斐尔

失败意味着它将通过填满mem[]字典来耗尽所有可用的系统内存。并感谢您清理我的答案。不太熟悉LaTeX,但下次会加倍努力。
2012年

您可以在编辑器框旁边的Markdown上找到帮助;请参阅此处以获取LaTeX入门。
拉斐尔

Answers:


14

不同的维度是独立的。您可以做的是,针对每个维度j,计算该维度中有多少不同的步行路程,这些步行路程需要步。让我们称该数字为。从您的问题中,您已经知道如何使用动态编程来计算这些数字。W j t tW(j,t)

现在,很容易计算在维度采取步的步行次数。您有 散布维度,以便维度采取的步骤总数为,并且对于每种方式,您都有步行。对这些求和求 现在,由于您只需要记住值,内存就处于控制之下。对于大的,时间的增长是多项式的,但是大多数计算机的时间要比内存多得多。tiiŤΠÑ1w ^Σ1+2+...+Ñ=中号中号(Nt1,t2,,tM)itiΠ1NW(i,ti)WjtN

t1+t2++tN=M(Mt1,t2,,tN) Πi=1NW(i,ti).
W(j,t)N

您可以做得更好。递归划分维度分为两个子集,和,以及计算有多少走有只使用在子维度,只是那些在。分别称这些数字为和。您得到的步行总数B A B W At W Bt ABABWA(t)WB(t)

t1+t2=M(Mt1)WA(t1)WB(t2).

嗨,彼得。好吧,那是缺少的见解。现在我只剩下一个疑问了。外部总和遍历总和为M的t1,t2,... tn的所有可能组合。不幸的是,此类组合的数量为C(M + 1,N-1),该数字可能高达C(300 + 1、10-9)。非常大的数字... :(
Alexandre

1
@Alexandre:我的第二种算法(以“您可以做得更好”开头)没有这个问题。我将第一个算法留在了答案中,因为它是我想出的第一个算法,并且因为我认为将第二个算法解释为第一个算法的变体要比没有任何动机地给出它要容易得多。
Peter Shor 2012年

我实现了第二种算法。它速度更快,但对于最大范围而言仍然太低。第一个算法的问题是对总和为M的t1,t2,t3,... tn的所有可能进行迭代。第二个算法仅对t1 + t2 = M的解进行迭代。但是,对于Wa (t1),迭代求解t1'+ t2'= t1。递归地依此类推。如果您有兴趣,这里是实现:pastebin.com/e1BLG7Gk。在第二种算法中,在t1,t2上应该有M个多项式吗?
2012年

没关系!解决了!还必须在set_ways函数中使用备忘录。这是最终的解决方案,快速发展!pastebin.com/GnkjjpBN 谢谢彼得的见解。您做出了两个主要观察:问题独立性和分而治之。我建议人们看一下我的解决方案,因为有些问题不在上面的答案中,例如W(i,ti)函数需要第三个参数,即位置。必须为i,ti和position的值组合计算该值。如果可以的话,还要在第二个算法中将多项式t2相加。
2012年

4

让我们从代码中提取的公式(对于内部单元格,它忽略边界情况):now(s,x1,,xn)

now(s,x1,,xn)=+i=0nnow(s1,x1,,xi1,xi+1,xi+1,,xn)+i=0nnow(s1,x1,,xi1,xi1,xi+1,,xn)

这里有一些想法。

  • 我们看到,一旦您计算了所有值,就可以删除所有计算值。s=ks<k
  • 对于固定,您应该按字典顺序计算表条目(仅因为它很简单)。然后,请注意,每个像元只需要在“一个半径”内的此类像元,即没有坐标可以比一个更远。因此,一旦迭代达到,就可以删除所有值。如果这还不够,请对执行相同的操作-对于固定的,在达到时删除和值-依此类推。xsx1=ix1i2x2x1=ix1=ix2j2x2=j
  • 请注意,“所以总是有可能的不同移动”仅在网格的中间成立,也就是说,对于所有,如果且。但是,这也意味着,答案很简单,中间:这只是。如果您具有有效的动态编程重复性,那么仅此一项就可以使您节省掉大部分表(如果)。2NxiM>0xi+M<Dii(2N)MMN
  • 需要注意的另一件事是,您不必计算整个表。无论如何,大多数值都将填充(如果)。您可以将自己限制在左右的边长的(超)立方体中(请注意,由于离开网格的路径,它会凹入)。中号« Ñ 2 中号X0MN2Mx

这应该足以使内存使用率保持在较低水平。


嗨,拉斐尔,假设我们的目标是在5x5x5的网格上现在(3,3,3,3)。使用动态编程并按照您的建议使用lex顺序,我们将先计算now(0,0,0,0),然后计算(0,0,0,1),... now(0,5,5,5)。在什么时候我们可以舍弃now(0,0,0,0)(这比(5,5,5)的半径大一倍,因为我们现在需要它来计算now(1、0、0) ,0),now(1、0、0、1)等吗?您提到M << N几次,但是界限是1 <= M <= 300,并且1 <= N <= 10。在极端,它似乎并不认为1 << 300
亚历山大

1)我的第二个子弹有什么不清楚的地方?一旦计算,就可以丢弃。不过,这不是最早可以丢弃;您需要的最后一个单元格是。2)老实说,我不太担心您对和特定值。我宁愿看看一般的问题。如果您没有,那么最后两个项目符号将无济于事。和应该足以注意到这种效果,而且任何一种策略都不会受到伤害。(2,0,0,0)(0,\*,\*,\*)(0,0,0,0)(1,0,0,0)MNMNM=1N=10
拉斐尔

1
1)我知道的子弹。这将空间复杂度从M * D ^ N降低到D ^ N,但是D ^ N仍然太多。我不太清楚2)项目符号的工作原理。您可以在我的评论中使用该示例进行说明吗?
2012年

@Alexandre我在之前的评论中做了。如果我将理解为,则应用第二个项目符号一次可以将空间复杂度降低到,第二次将空间复杂度降低到和以此类推。(更准确地说,它从变为,依此类推。)max i = 1 N D i D N 1 D N 2N i = 1 D i N i = 2 D iDmaxi=1,,NDiDN1DN2i=1NDii=2NDi
拉斐尔

不太了解该怎么做...说我确实了解了,而且我将空间复杂性降低到D。从根本上讲,是否仍需要解决M * D ^ N个子问题?使多项式成为多项式不是必需的吗?
2012年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.