Pemrograman dinamis dengan sejumlah besar masalah

11

Pemrograman dinamis dengan sejumlah besar masalah. Jadi saya mencoba menyelesaikan masalah ini dari Interview Street:

Grid Walking (Nilai 50 poin)
Anda berada di grid N dimensi pada posisi (x1,x2,,xN) . Dimensi grid adalah (D1,D2,,DN ). Dalam satu langkah, Anda dapat berjalan satu langkah di depan atau di belakang di salah satu dari dimensi N(Jadi selalu ada 2N gerakan yang mungkin berbeda). Berapa banyak cara yang bisa Anda ambil Mlangkah-langkah sedemikian rupa sehingga Anda tidak meninggalkan grid di titik mana pun? Anda meninggalkan kotak jika untuk xi , baik xi0 atau xi>Di .

Percobaan pertama saya adalah solusi rekursif memoised ini:

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

Kejutan besar: gagal untuk sejumlah besar langkah dan / atau dimensi karena kurangnya memori.

Jadi langkah selanjutnya adalah meningkatkan solusi saya dengan menggunakan pemrograman dinamis. Tetapi sebelum memulai, saya melihat masalah besar dengan pendekatan tersebut. Argumennya starting_pointadalah n -tuple, di mana n sebesar 10 . Jadi sebenarnya, fungsinya bisa number_of_ways(steps, x1, x2, x3, ... x10)dengan 1xi100 .

Masalah pemrograman dinamis yang saya lihat di buku pelajaran hampir semuanya memiliki variabel twp, sehingga hanya diperlukan matriks dua dimensi. Dalam hal ini, matriks sepuluh dimensi akan diperlukan. Jadi sel total.10010

Dengan matriks 2-D dalam pemrograman dinamis, biasanya hanya baris perhitungan sebelumnya yang diperlukan untuk perhitungan selanjutnya, sehingga mengurangi kompleksitas spasial dari ke min ( m , n ) . Saya tidak yakin bagaimana saya akan melakukan hal yang sama dalam kasus ini. Memvisualisasikan tabel tidak layak, jadi jawabannya harus datang langsung dari rekursi di atas.mnmin(m,n)

MEMPERBARUI

Menggunakan saran Peter Shor, dan membuat beberapa koreksi kecil, terutama kebutuhan untuk melacak posisi dalam fungsi , dan bukannya hanya membelah dimensi menjadi dua set A dan B, melakukan pemisahan secara rekursif, secara efektif menggunakan metode divide-and-conquer, hingga base case tercapai di mana hanya satu dimensi berada di set.W(i,ti)

Saya datang dengan implementasi berikut, yang lulus semua tes di bawah waktu eksekusi maksimum:

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
Alexandre
sumber
2
"Gagal untuk sejumlah besar langkah dan / atau dimensi" - apa artinya "gagal" di sini?
Raphael
1
Selamat datang! Saya mengedit pertanyaan Anda untuk a) menggunakan format Markdown dan LaTeX yang tepat (harap buat sendiri di masa mendatang) dan b) hapus selokan berlebihan. Kami tidak peduli dengan uraian kode-C; tolong batasi diri Anda pada gagasan , yaitu kode semu dari hal-hal utama.
Raphael
Gagal berarti menghabiskan semua memori sistem yang tersedia dengan mengisi mem[]kamus. Dan terima kasih telah membersihkan jawaban saya. Tidak terlalu terbiasa dengan LaTeX tetapi akan berusaha di waktu berikutnya.
Alexandre
Anda dapat menemukan bantuan pada Penurunan harga di sebelah kotak editor; lihat di sini untuk primer di LaTeX.
Raphael

Jawaban:

14

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

Anda dapat melakukan yang lebih baik lagi. Secara rekursif membagi dimensi menjadi dua himpunan bagian, dan , dan menghitung berapa banyak berjalan ada hanya menggunakan dimensi di bagian , dan hanya orang-orang di . Sebut angka-angka ini dan , masing-masing. Anda mendapatkan jumlah total jalan kaki denganABABWA(t)WB(t)

t1+t2=M(Mt1)WA(t1)WB(t2).
Peter Shor
sumber
Hai Peter. Baiklah, itu adalah wawasan yang hilang. Sekarang saya hanya punya satu keraguan tersisa. Jumlah luar iterates atas semua kemungkinan kombinasi t1, t2, ... tn jumlah itu ke M. Sayangnya, jumlah kombinasi tersebut adalah C (M + 1, N-1), yang bisa setinggi C (300). +1, 10-9). Jumlah yang sangat besar ... :(
Alexandre
1
@Alexandre: Algoritme kedua saya (dimulai dengan "Anda dapat melakukan lebih baik") tidak memiliki masalah itu. Saya meninggalkan algoritma pertama dalam jawaban saya karena ini adalah yang pertama saya buat, dan karena saya pikir akan lebih mudah untuk menjelaskan algoritma kedua sebagai varian yang pertama daripada hanya memberikannya tanpa motivasi.
Peter Shor
Saya menerapkan algoritma kedua. Ini lebih cepat, tetapi masih terlalu rendah untuk batas terbesar. Masalah dengan yang pertama adalah iterasi pada semua kemungkinan t1, t2, t3, ... tn yang dijumlahkan ke M. Algoritma kedua hanya beralih pada solusi ke t1 + t2 = M. Tapi kemudian hal yang sama harus dilakukan untuk Wa (t1), mengulangi solusi ke t1 '+ t2' = t1. Dan sebagainya secara rekursif. Inilah implementasinya jika Anda dihasut: pastebin.com/e1BLG7Gk . Dan dalam algoritma kedua, ada multinomial harus M lebih dari t1, t2 tidak?
Alexandre
Sudahlah! Dipecahkan! Harus menggunakan memoisasi dalam fungsi set_ways juga. Inilah solusi terakhir, yang sangat cepat! pastebin.com/GnkjjpBN Terima kasih atas wawasan Anda, Peter. Anda membuat kedua pengamatan utama: masalah independensi dan divide-and-menaklukkan. Saya sarankan orang melihat solusi saya karena ada beberapa hal yang tidak ada dalam jawaban di atas, seperti fungsi W (i, ti) yang membutuhkan argumen ketiga, yaitu posisi. Itu harus dihitung untuk kombinasi nilai i, ti, dan posisi. Jika Anda bisa, tambahkan juga t2 multinomial dalam algoritma kedua Anda.
Alexandre
4

Mari kita ekstrak formula untuk dari kode Anda (untuk sel dalam, yang mengabaikan kasus perbatasan):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)

Berikut ini beberapa ide.

  • Kami melihat bahwa setelah Anda menghitung semua nilai untuk , Anda dapat menghapus semua nilai yang dihitung untuk .s=ks<k
  • Untuk tetap , Anda harus menghitung entri tabel di urutan leksikografis (hanya karena itu sederhana). Kemudian, perhatikan bahwa setiap sel hanya membutuhkan sel-sel tersebut di dalam "jari-jari satu", yang tidak ada koordinat dapat lebih jauh dari satu. Karenanya, setelah iterasi Anda mencapai , Anda dapat memberikan semua nilai untuk . Jika itu tidak cukup, lakukan hal yang sama untuk - untuk tetap , drop nilai dengan dan ketika tercapai - dan seterusnya.sx1=ix1i2x2x1=ix1=ix2j2x2=j
  • Perhatikan bahwa "jadi selalu ada gerakan yang mungkin berbeda" hanya berlaku di tengah kisi, yaitu jika dan untuk semua . Tapi itu juga berarti bahwa jawabannya mudah di tengah-tengah: itu hanya . Jika Anda memiliki pengulangan pemrograman dinamis yang berfungsi, itu saja akan memungkinkan Anda mencukur habis sebagian besar tabel (jika ).2NxiM>0xi+M<Dii(2N)MMN
  • Hal lain yang perlu diperhatikan adalah Anda tidak perlu menghitung seluruh tabel; sebagian besar nilai akan diisi dengan pula (jika ). Anda dapat membatasi diri pada kubus (hiper) dengan panjang sekitar (perhatikan bahwa itu akan berubah karena jalur yang meninggalkan kisi).0MN2Mx

Itu harus cukup untuk menjaga penggunaan memori cukup rendah.

Raphael
sumber
Hai Raphael, katakanlah tujuan kita sekarang (3, 3, 3, 3), di grid 5x5x5. Menggunakan pemrograman dinamis dan menggunakan urutan lex seperti yang Anda sarankan, kami akan menghitung sekarang (0, 0, 0, 0), lalu (0, 0, 0, 1), ... sekarang (0, 5, 5, 5). Pada titik mana kita dapat membuang sekarang (0, 0, 0, 0) (yang lebih dari satu radius dari (5, 5, 5), karena kita sekarang akan memerlukannya untuk menghitung sekarang (1, 0, 0 , 0), sekarang (1, 0, 0, 1), dll? Anda menyebutkan M << N beberapa kali, tetapi batasannya adalah 1 <= M <= 300, dan 1 <= N <= 10. Jadi , pada ekstremnya, sepertinya 1 << 300.
Alexandre
1) Apa yang tidak jelas dalam peluru kedua saya? Segera setelah Anda menghitung , Anda dapat membuang . Itu bukan titik awal di mana Anda dapat membuang , meskipun; sel terakhir yang Anda butuhkan adalah . 2) Saya tidak terlalu peduli dengan nilai spesifik Anda untuk dan , jujur ​​saja. Saya lebih suka melihat masalah umum. Jika Anda tidak memiliki , dua peluru terakhir tidak akan banyak membantu Anda. dan harus cukup untuk melihat efeknya, dan strategi tidak pernah sakit. (2,0,0,0)(0,\*,\*,\*)(0,0,0,0)(1,0,0,0)MNMNM=1N=10
Raphael
1
1) peluru yang saya mengerti. Itu mengurangi kompleksitas spasial dari M * D ^ N ke D ^ N, tetapi D ^ N masih terlalu banyak. Saya tidak melihat bagaimana 2) peluru bekerja. Bisakah Anda menggunakan contoh dalam komentar saya untuk menggambarkannya?
Alexandre
@Alexandre saya lakukan di komentar saya sebelumnya. Jika saya membaca sebagai makna , maka menerapkan peluru kedua sekali mengurangi kompleksitas ruang menjadi , yang kedua menjadi dan begitu seterusnya. (Lebih tepatnya, ia beralih dari ke dan sebagainya.)maks i = 1 , , N D i D N - 1 D N - 2N i = 1 D i N i = 2 D iDmaxi=1,,NDiDN1DN2i=1NDii=2NDi
Raphael
Tidak cukup memahami bagaimana melakukannya ... Katakanlah saya memang mengerti, dan bahwa saya mengurangi kompleksitas spasial menjadi D. Pada dasarnya, bukankah subproblem M * D ^ N masih perlu dipecahkan? Bukankah properti tambahan diperlukan untuk membuat masalah jumlahnya banyak?
Alexandre