多数の副問題を伴う動的プログラミング


11

多数の副問題がある動的プログラミング。だから私はインタビューストリートからこの問題を解決しようとしています:

グリッド・ウォーキング(50点のスコア)
あなたはに位置しているの位置に次元グリッド。グリッドの次元は)です。1つのステップで、次元のいずれかを1つ前または後ろに歩くことができます。(したがって、常に可能な異なる動きがあります)。どの時点でもグリッドを離れないように、ステップをいくつ実行できますか?あなたは、任意のためならば、グリッドを離れる、どちらかまたは。X 1はxは2... xはND 1D 2... D N N 2 N M X I 、X I0 X I > DのIを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はタプルで、は大きさです。だから、実際には、関数は可能性で 。、N 10 1 X I100nn10number_of_ways(steps, x1, x2, x3, ... x10)1xi100

私が教科書で見た動的プログラミングの問題はほとんどすべてtwp変数を持っているので、2次元のマトリックスだけが必要です。この場合、10次元のマトリックスが必要になります。そう合計細胞。10010

動的計画法の2次元行列では、通常、次の計算に必要なのは前の行の計算のみなので、空間の複雑さがから減少します。この場合、同じようにする方法がわかりません。テーブルを視覚化することは現実的ではないため、答えは上記の再帰から直接取得する必要があります。mnmin(m,n)

更新

Peter Shorの提案を使用し、特に関数で位置を追跡する必要性など、いくつかのマイナーな修正を行い、次元を2つのセットAとBに分割するだけでなく、再帰的に分割を行い、効果的に分割統治法。1つの次元のみがセットに含まれる基本ケースに到達するまで。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についてあまり詳しくないが、次回は努力する。
アレクサンドル

エディターボックスの横にあるMarkdownでヘルプを見つけることができます。LaTeXの入門書はこちらをご覧ください。
ラファエル

回答:


14

異なる次元は独立しています。実行できることは、各次元jについて、その次元にステップを実行するさまざまなウォークがいくつあるかを計算することです。その数をと呼びましょう。あなたの質問から、あなたはすでに動的プログラミングでこれらの数値を計算する方法を知っています。tW(j,t)

これで、ディメンションステップの歩行数を簡単にカウントます。あなたは持っている 次元で撮影した総ステップ数がそうすることを点在さ寸法の方法をある、およびこれらの方法のそれぞれのためにあなたが持っている歩きます。これらを合計して これで、値を覚えるだけでよいので、メモリが制御されます。大きな場合、時間は超多項的に増加しますが、ほとんどのコンピュータはメモリよりも多くの時間を持っています。tii(Nt1,t2,,tM)itiΠ1NW(i,ti)

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

あなたはもっと上手にできる。再帰的に2つのサブセット、に寸法を分割と、及び計算は、どのように多くのサブセットにだけ寸法を使用しているが歩く、ちょうどそれらに。これらの番号をおよびとます。合計歩行数を取得しますABABWA(t)WB(t)

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

こんにちはピーター。さて、それは失われた洞察でした。今、私は1つだけの疑いを残しています。外側の合計は、合計がMになるt1、t2、... tnのすべての可能な組み合わせを反復処理します。残念ながら、そのような組み合わせの数はC(M + 1、N-1)であり、C(300 + 1、10-9)。非常に大きな数... :(
アレクサンドル

1
@Alexandre:私の2番目のアルゴリズム(「もっと上手にできる」で始まる)にはその問題はありません。最初のアルゴリズムは最初に思いついたものであり、2番目のアルゴリズムを1番目のアルゴリズムのバリエーションとして説明する方が、動機なしで単に与えるよりもはるかに簡単なので、私は最初のアルゴリズムを私の答えに残しました。
Peter Shor

2番目のアルゴリズムを実装しました。それはより高速ですが、それでも最大の境界に対して低すぎます。最初の問題は、t1、t2、t3、... tnのすべての可能性について反復してMにしたものです。2番目のアルゴリズムは、t1 + t2 = Mの解についてのみ反復します。ただし、Waについても同じことが必要です(t1)、t1 '+ t2' = t1の解を反復します。など、再帰的に。ここでは、insterestedしている場合の実装です:pastebin.com/e1BLG7Gk。そして2番目のアルゴリズムでは、多項式はt1、t2以上のMでなければなりませんか?
アレクサンドル

気にしないで!解決しました!set_ways関数でもメモ化を使用する必要がありました。これが非常に高速な最終的な解決策です!pastebin.com/GnkjjpBN 洞察力をありがとうございますPeter。あなたは両方の重要な観察をしました:問題の独立性と分割統治法。W(i、ti)関数が3番目の引数(位置)を必要とするなど、上記の答えにはないものがあるため、私のソリューションを検討することをお勧めします。これは、i、ti、および位置の値の組み合わせに対して計算する必要があります。可能であれば、2番目のアルゴリズムで多項式t2も追加します。
アレクサンドル

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
  • 固定場合、テーブルエントリを辞書式順序で計算する必要があります(単純なため)。次に、すべてのセルは「1つの半径」内のセルのみを必要とすることに注意してください。つまり、座標は1よりも遠くにあることはできません。したがって、反復がにすると、すべての値を削除できます。不十分な場合は、についても同じことを行います。固定場合、に達したときにおよび値をドロップします。sx1=ix1i2x2x1=ix1=ix2j2x2=j
  • 「したがって、常に可能な動きがある」ことは、グリッドの中央でのみ保持されることに注意してください。つまり、すべてのに対しておよび場合です。しかし、それはまた、答えが途中で簡単であることも意味します。それはです。動的プログラミングの繰り返しが機能している場合、それだけでテーブルのほとんどを削ることができます(場合)。2NxiM>0xi+M<Dii(2N)MMN
  • 注意すべきもう1つの点は、テーブル全体を計算する必要がないことです。とにかく、ほとんどの値はで埋められます(場合)。中心としたエッジ長の(ハイパー)キューブに制限できます(グリッドから出るパスのために凹んでいることに注意してください)。0MN2Mx

これで、メモリ使用量をかなり低く抑えることができます。


こんにちはラファエル、私たちの目標がグリッド(5、5、5)に今(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)から1を超える半径)を破棄できるようになったのは、今すぐ計算する必要があるためです(1、0、0 、0)、now(1、0、0、1)など?M << Nを数回言及しましたが、範囲は1 <= M <= 300、および1 <= N <= 10です。 、極端な場合、1 << 300とは思われません
アレクサンドル

1)2番目の箇条書きで不明確なものは何ですか?を計算するとすぐに、を破棄できます。ただし、これは破棄できる最も早い時点ではありません。あなたがそれを必要とする最後のセルはです。2)正直に言うと、と具体的な値についてあまり気にしていません。私はむしろ一般的な問題を見たいと思います。がない場合、最後の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)弾丸がどのように機能するかはよくわかりません。コメントの例を使用してそれを説明できますか?
アレクサンドル

@Alexandre以前のコメントでやった。をと解釈した場合、2番目の箇条書きを一度適用すると、スペースの複雑さがに減少し、2回目は減少し、など。(より正確には、からます。)Dmaxi=1,,NDiDN1DN2i=1NDii=2NDi
ラファエル

それを行う方法を完全に把握していません...私は理解し、空間的な複雑さをDに減らしたとしましょう。基本的に、M * D ^ Nサブ問題はまだ解決する必要はありませんか?問題の多項式を作成するために追加のプロパティが必要ではありませんか?
アレクサンドル
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.