การเขียนโปรแกรมแบบไดนามิกที่มีปัญหาย่อยจำนวนมาก


11

การเขียนโปรแกรมแบบไดนามิกที่มีปัญหาย่อยจำนวนมาก ดังนั้นฉันพยายามแก้ไขปัญหานี้จาก Interview Street:

ตารางเดิน (คะแนน 50 คะแนน)
คุณกำลังตั้งอยู่ในตารางมิติที่ตำแหน่งX_n) ขนาดของกริดคือ ) ในขั้นตอนเดียวคุณสามารถเดินไปข้างหน้าหรือข้างหลังหนึ่งก้าวในมิติใด ๆ ก็ได้ (ดังนั้นจะมีการเคลื่อนไหวต่าง ๆ ที่เป็นไปได้เสมอ) คุณสามารถทำตามขั้นตอนหลายวิธีเช่นนี้โดยที่คุณไม่ออกจากกริดในทุกจุด คุณออกจากตารางถ้าใด ๆทั้งหรือD_i( x 1 , x 2 , , x N ) ( D 1 , D 2 , , D N N 2 N M x ฉันx ฉัน0 x i > 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เป็น -tuple ที่คือขนาดใหญ่เป็น10ดังนั้นในความเป็นจริงการทำงานอาจจะมี 100n 10 1 x ฉัน100nn10number_of_ways(steps, x1, x2, x3, ... x10)1xi100

ปัญหาการเขียนโปรแกรมแบบไดนามิกที่ฉันเคยเห็นในตำราเกือบทั้งหมดมีตัวแปร twp ดังนั้นจำเป็นต้องใช้เมทริกซ์สองมิติเท่านั้น ในกรณีนี้จำเป็นต้องใช้เมทริกซ์สิบมิติ ดังนั้นทั้งหมดเซลล์โดยรวม10010

มี 2-D matrixes ในการเขียนโปรแกรมแบบไดนามิกมักจะมีเพียงแถวก่อนหน้าของการคำนวณเป็นสิ่งจำเป็นสำหรับการคำนวณต่อไปดังนั้นการลดความซับซ้อนเชิงพื้นที่จากเพื่อn) ฉันไม่แน่ใจว่าฉันจะทำเช่นเดียวกันในกรณีนี้ได้อย่างไร การแสดงตารางเป็นไปไม่ได้ดังนั้นคำตอบจะต้องมาจากการสอบถามซ้ำข้างต้นโดยตรงนาที( ม. , n )mnmin(m,n)

UPDATE

ใช้คำแนะนำของ Peter Shor และทำการแก้ไขเล็กน้อยโดยเฉพาะความต้องการติดตามตำแหน่งในฟังก์ชั่นและแทนที่จะแยกมิติออกเป็นสองชุด A และ B เท่านั้นทำการแยกแบบซ้ำโดยใช้ วิธีการหารและพิชิตจนกว่าจะถึงกรณีฐานที่มีเพียงหนึ่งมิติอยู่ในชุด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; โปรด จำกัด ตัวเองให้กับแนวคิดซึ่งเป็นรหัสหลอกๆของสิ่งสำคัญ
Raphael

Fails หมายความว่าหมดความทรงจำของระบบที่มีอยู่ทั้งหมดโดยการเติมmem[]พจนานุกรม และขอบคุณที่ทำความสะอาดคำตอบของฉัน ไม่คุ้นเคยกับ LaTeX มากเกินไป แต่จะพยายามครั้งต่อไป
Alexandre

คุณสามารถค้นหาความช่วยเหลือเกี่ยวกับ Markdown ถัดจากช่องแก้ไข ดูที่นี่สำหรับสีรองพื้นบน LaTeX
กราฟิลส์

คำตอบ:


14

ขนาดที่แตกต่างกันเป็นอิสระ สิ่งที่คุณสามารถทำได้คือการคำนวณสำหรับแต่ละมิติเจหลายวิธีที่แตกต่างกันเดินที่มีอยู่ในเพียงว่ามิติที่ใช้ขั้นตอน ขอให้เราโทรไปยังหมายเลขที่t) จากคำถามของคุณคุณรู้วิธีคำนวณตัวเลขเหล่านี้ด้วยการเขียนโปรแกรมแบบไดนามิกW ( j , t )tW(j,t)

ตอนนี้มันเป็นเรื่องง่ายที่จะนับจำนวนของการเดินที่เกิดขั้นตอนในมิติฉันคุณมี วิธีการ interspersing มิติเพื่อให้จำนวนรวมของขั้นตอนการดำเนินการในมิติที่เป็นและสำหรับแต่ละวิธีการเหล่านี้คุณมีเดิน รวมผลรวมเหล่านี้เพื่อรับ ขณะนี้หน่วยความจำอยู่ภายใต้การควบคุมเนื่องจากคุณจะต้องจดจำค่าเท่านั้น เวลาเพิ่มขึ้นอย่างมากสำหรับขนาดใหญ่แต่คอมพิวเตอร์ส่วนใหญ่มีเวลามากกว่าหน่วยความจำมาก i ( NtiiฉันtฉันΠN1W(i,ti)t1+t2++tN=M(M(Nt1,t2,,tM)itiΠ1NW(i,ti)

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

คุณสามารถทำได้ดียิ่งขึ้น ซ้ำแบ่งขนาดเป็นสองส่วนย่อย, และและคำนวณหลายวิธีเดินมีใช้เพียงมิติในเซตและเพียงแค่ผู้ที่อยู่ในBเรียกหมายเลขเหล่านี้และตามลำดับ คุณได้รับจำนวนทั้งหมดของการเดินABABWA(t)WB(t)

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

สวัสดีปีเตอร์ เอาล่ะนั่นคือความเข้าใจที่ขาดหายไป ตอนนี้ฉันเหลือเพียงข้อสงสัย ผลรวมด้านนอกจะวนซ้ำตามชุดค่าผสมที่เป็นไปได้ทั้งหมดของ t1, t2, ... tn ที่รวมกับ M. น่าเสียดายที่จำนวนชุดค่าผสมดังกล่าวคือ C (M + 1, N-1) ซึ่งอาจสูงถึง C (300 +1, 10-9) จำนวนมาก ... :(
Alexandre

1
@Alexandre: อัลกอริทึมที่สองของฉัน (เริ่มต้นด้วย "คุณสามารถทำได้ดียิ่งขึ้น") ไม่มีปัญหานั้น ฉันออกจากอัลกอริทึมแรกในคำตอบของฉันเพราะมันเป็นอันแรกที่ฉันคิดขึ้นมาและเพราะฉันคิดว่ามันง่ายกว่าที่จะอธิบายอัลกอริธึมที่สองเป็นตัวแปรของอันแรกมากกว่าแค่ให้มันโดยไม่มีแรงจูงใจใด ๆ
Peter Shor

ฉันใช้อัลกอริทึมที่สอง มันเร็วกว่า แต่ก็ยังต่ำเกินไปสำหรับขอบเขตที่ใหญ่ที่สุด ปัญหาของอันแรกคือวนซ้ำสมบัติทั้งหมดของ t1, t2, t3, ... tn ที่รวมกับเอ็มอัลกอริธึมที่สองเท่านั้นวนซ้ำวิธีแก้ปัญหา t1 + t2 = M แต่ต้องทำเช่นเดียวกันสำหรับวา (t1) วนซ้ำโซลูชันไปที่ t1 '+ t2' = t1 และอื่น ๆ ซ้ำ ๆ นี่คือการดำเนินการในกรณีที่คุณกำลัง insterested: pastebin.com/e1BLG7Gk และในอัลกอริธึมที่สองมีมัลติโนเมียลควรเป็น M ส่วน t1, t2 ไม่?
Alexandre

ไม่เป็นไร! แก้ไขมัน! จำเป็นต้องใช้การบันทึกความจำในฟังก์ชั่น set_ways เช่นกัน นี่คือทางออกสุดท้ายซึ่งเร็วที่สุด! pastebin.com/GnkjjpBN ขอขอบคุณสำหรับความเข้าใจของคุณปีเตอร์ คุณทำทั้งข้อสังเกตที่สำคัญ: ความเป็นอิสระของปัญหาและการแบ่งและพิชิต ฉันแนะนำให้คนดูวิธีแก้ปัญหาของฉันเพราะมีบางสิ่งที่ไม่ได้อยู่ในคำตอบข้างต้นเช่นฟังก์ชั่น W (i, Ti) ที่ต้องการอาร์กิวเมนต์ที่สามซึ่งเป็นตำแหน่ง ที่จะต้องมีการคำนวณสำหรับการรวมค่าของ i, ti และตำแหน่ง หากคุณทำได้ให้เพิ่ม t2 มัลติโนเมียลในอัลกอริทึมที่สองของคุณ
Alexandre

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)

นี่คือแนวคิดบางส่วน

  • เราจะเห็นว่าเมื่อคุณได้คำนวณค่าทั้งหมดสำหรับคุณสามารถวางค่าคำนวณทั้งหมด<ks=ks<k
  • สำหรับที่มีการแก้ไขคุณควรคำนวณรายการตารางตามลำดับพจนานุกรม (เพียงเพราะมันง่าย) จากนั้นโปรดทราบว่าทุกเซลล์ต้องการเพียงเซลล์ดังกล่าวภายใน "รัศมีหนึ่ง" ซึ่งไม่มีพิกัดใดที่จะห่างออกไปมากกว่าหนึ่ง ดังนั้นเมื่อฮิตซ้ำของคุณคุณสามารถวางค่าทั้งหมดสำหรับI-2 หากยังไม่เพียงพอให้ทำเช่นเดียวกันสำหรับ - สำหรับคงที่แล้วปล่อยค่าด้วยและเมื่อถึง - เป็นต้นsx1=ix1i2x2x1=ix1=ix2j2x2=j
  • โปรดทราบว่า "เพื่อให้มีเสมอการเคลื่อนไหวที่แตกต่างกันไปได้" ถือเฉพาะในช่วงกลางของตารางที่คือถ้าและสำหรับฉันแต่นั่นก็หมายความว่าคำตอบเป็นเรื่องง่ายที่อยู่ตรงกลาง: มันเป็นเพียงแค่ M หากคุณมีการเขียนโปรแกรมแบบไดนามิกที่เกิดขึ้นซ้ำเพียงอย่างเดียวนั่นจะช่วยให้คุณสามารถกำจัดตารางส่วนใหญ่ (ถ้า )2NxiM>0xi+M<Dii(2N)MMN
  • สิ่งที่ควรทราบอีกประการหนึ่งคือคุณไม่จำเป็นต้องคำนวณทั้งตาราง ค่าส่วนใหญ่จะถูกเติมด้วยอย่างไรก็ตาม (ถ้า ) คุณสามารถ จำกัด ตัวเองให้เป็นลูกบาศก์ (ไฮเปอร์) ของความยาวขอบรอบ ๆ (โปรดทราบว่ามันจะเว้าแหว่งเนื่องจากเส้นทางออกจากตาราง)0MN2Mx

นั่นควรจะเพียงพอที่จะทำให้การใช้หน่วยความจำค่อนข้างต่ำ


สวัสดี Raphael สมมติว่าเป้าหมายของเราคือตอนนี้ (3, 3, 3, 3) บนตาราง 5x5x5 การใช้การเขียนโปรแกรมแบบไดนามิกและการใช้คำสั่ง lex ตามที่คุณแนะนำเราจะคำนวณทันที (0, 0, 0, 0) จากนั้น (0, 0, 0, 1), ... ตอนนี้ (0, 5, 5, 5) ตอนนี้เราสามารถยกเลิกได้ (0, 0, 0, 0) (ซึ่งมากกว่ารัศมีหนึ่งห่างจาก (5, 5, 5) เนื่องจากเราต้องการคำนวณตอนนี้ (1, 0, 0 , 0), ตอนนี้ (1, 0, 0, 1) ฯลฯ คุณพูดถึง M << N สองสามครั้ง แต่ขอบเขตคือ 1 <= M <= 300, และ 1 <= N <= 10 ดังนั้น ที่สุดขั้วดูเหมือนจะไม่เป็นเช่นนั้น 1 << 300
อเล็กซานเดอร์

1) อะไรคือความไม่ชัดเจนในกระสุนนัดที่สองของฉัน เร็วที่สุดเท่าที่คุณคำนวณคุณสามารถทิ้ง*) นั่นไม่ใช่จุดแรกสุดที่คุณสามารถทิ้งแต่; เซลล์สุดท้ายที่คุณต้องการสำหรับมี(1,0,0,0)2) ฉันไม่ได้กังวลเกี่ยวกับค่าเฉพาะของคุณสำหรับและมากเกินไปที่จะซื่อสัตย์ ฉันอยากดูปัญหาทั่วไป หากคุณไม่มีกระสุนสองนัดสุดท้ายจะไม่ช่วยคุณเท่าไหร่ และควรพอเพียงที่จะสังเกตเห็นถึงผลกระทบที่เกิดขึ้นและไม่กระทบต่อกลยุทธ์ (2,0,0,0)(0,\*,\*,\*)(0,0,0,0)(1,0,0,0)MNMNM=1N=10
Raphael

1
1) bullet ที่ฉันเข้าใจ นั่นช่วยลดความซับซ้อนเชิงพื้นที่จาก M * D ^ N เป็น D ^ N แต่ D ^ N ยังคงมากเกินไป ฉันไม่ค่อยเห็นว่ากระสุน 2) ทำงานอย่างไร คุณสามารถใช้ตัวอย่างในความคิดเห็นของฉันเพื่อแสดงมันได้หรือไม่
Alexandre

@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 จะยังคงต้องแก้ไขหรือไม่ ไม่จำเป็นต้องมีคุณสมบัติเพิ่มเติมเพื่อสร้างปัญหาพหุนาม
Alexandre
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.